022: The Fast Fourier Transform
2026-10-19
Last time every point in the pattern was a sum. To find how much of one ripple a gap contains you multiply the gap by the ripple, cell by cell and add up. The gap is 512 by 512 cells and the pattern has a point for each of 512 by 512 ripples. That is a sum over 262,144 cells for each of 262,144 points, about 69 billion multiplications for one picture.
The GPU does a picture in a fraction of a millisecond, and the readout under the canvas gives the time on this machine. No GPU does 69 billion multiplications that fast, so it is not doing it that way. This post is how it does it.
The gap is on the left below. On the right is the transform part-way through, the same brightness scale as last time. Pass is how far along it is: 0 is the gap itself and 18 is the finished pattern. It runs by itself from 0 to 18 when the page loads. Drag Pass to take over.
Try these:
- Drag Pass slowly from 0 to 18. Each pass joins pairs of small transforms into bigger ones. Part-way through, the picture is part where and part which ripple.
- Passes 1 to 9 work along the rows and 10 to 18 down the columns. After 9 every row of the gap has become its own pattern of fringes and the picture is a band. The last nine spread the band up and down.
- Try the Circle. The same 18 passes on a different gap.
Analysis
How long a transform takes, measured in this browser. The red and grey lines are a line of numbers on the CPU. By the definition the time grows as the square of the length: twice as many numbers take four times as long. The fast method takes barely more than twice as long. At two thousand numbers it is already a couple of hundred times quicker, which is about what the step counts predict: 2,048 steps for each of 2,048 results against about 11. The difference keeps widening: for a million numbers it would be about fifty thousand to one.
The blue line is the GPU, which transforms a whole picture at once. A 512 by 512 picture is 262,144 numbers, 128 times as many as a line of two thousand. Compare its time with the CPU’s for that line: it is nowhere near 128 times longer. At the small sizes the blue line sits above the others, since getting the work started costs more than the work.
Gauss and a Cold War
In about 1805, Carl Friedrich Gauss was working out the orbits of the asteroids Pallas and Juno from a handful of observations and needed the ripples that the measurements were made of. He found a way to do it with far fewer sums, wrote it up and did not publish it. It turned up in his collected works long after his death.
It was found again, more than once. The time it stuck was in 1963 and it was because of the Cold War. John Tukey was at a meeting of President Kennedy’s science advisory committee, which was talking about how to detect underground nuclear tests from seismometers placed in the ground. The measurements needed transforms that were too slow to calculate. Tukey had a way to do them with far fewer sums. Richard Garwin, who was at the meeting, saw what it could do and passed it to James Cooley at IBM to turn into a program. They published it in 1965.
This is the first paper on Nick Trefethen’s list of 13 classic papers in applied mathematics: Cooley and Tukey, 1965.
The shortcut
A line of N numbers x_j has the transform
X_k = \sum_{j=0}^{N-1} x_j \, e^{-2\pi i j k / N},
which is N sums of N terms each, N^2 in all. Split the x_j into the even-numbered ones and the odd-numbered ones and each half has a transform of its own, E_k and O_k, that is only N/2 numbers long. Putting them back together takes one multiplication each:
X_k = E_k + e^{-2\pi i k / N} O_k, \qquad X_{k+N/2} = E_k - e^{-2\pi i k / N} O_k.
That joins two half-size transforms into a whole one for the price of about N steps and each half can be split the same way, down to lines of one number, which are their own transform. There are \log_2 N levels of N steps. A million numbers needs about twenty million steps and not a million million.
You can see the levels in the picture. After s passes along a row the row is made of blocks 2^s long and each block is a small transform of every (N/2^s)th number of the line. At the start there are 512 blocks of one number each, which is the line itself. At the end there is one block of 512, which is the whole transform. Each pass halves the number of blocks and doubles the length of each, trading a doubling of where for a doubling of which ripple.
How the computer does it
A picture is a two-dimensional transform. It is done on the rows and then on the columns, each of them a line like the one above, so there are 2 \log_2 512 = 18 passes over the 512 by 512 grid.
Each pass is the joining step above. One thread on the GPU takes one pair of numbers and writes out the two they combine to. The passes alternate between two arrays, each one reading the last one’s result and writing into the other, so the numbers come out in order without a separate shuffle. This form is named for Stockham and the whole of it is in lib/fft.js. All 262,144 numbers go through each pass at once. The slider stops after the first few passes and shows whichever array has the half-finished result.
Sources
- Cooley, J. W. and Tukey, J. W. (1965), “An algorithm for the machine calculation of complex Fourier series”, Mathematics of Computation 19, 297 to 301, doi:10.1090/S0025-5718-1965-0178586-1.
- Heideman, M. T., Johnson, D. H. and Burrus, C. S. (1984), “Gauss and the history of the fast Fourier transform”, IEEE ASSP Magazine 1 (4), 14 to 21.
- IEEE, First demonstration of the fast Fourier transform, 1964, for the story of the 1963 meeting.