Skip to content

project-gemmi/benchmarking-fft

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Comparison of Fast Fourier Transform Libraries

FFTW library has an impressive list of other FFT libraries that FFTW was benchmarked against. Unfortunately, this list has not been updated since about 2005, and the situation has changed. (Update: Steven Johnson showed a new benchmark during JuliaCon 2019. In his hands FFTW runs slightly faster than Intel MKL. In my hands MKL is ~50% faster. Maybe I didn't squeeze all the performance from FFTW.)

FFTW is not the fastest one anymore, but it still has many advantages and it is the reference point for other libraries.

MKL (Intel Math Kernel Library) FFT is significantly faster. It's not open-source, but it is freely redistributable. MKL has fantastic compatibility with FFTW (no need to change the code, you just link it with MKL instead of fftw3) and with NumPy (no need to change the code, just do pip install mkl-fft).

KFR also claims to be faster than FFTW, but I read that in the latest version (3.0) it requires Clang for top performance, so I didn't benchmark it.

FFTS (South) and FFTE (East) are reported to be faster than FFTW, at least in some cases. I'd benchmark them if I had more time.

muFFT and pffft seem to have performance comparable to FFTW while being much simpler.
Update: there is also PGFFT in this category.

Libraries that are not vectorized such as KissFFT, meow_fft tend to be slower, but are also worth considering.

PocketFFT is vectorized only for multi-dimensional transforms (or for doing multiple 1D transforms). Unlike in other projects, it uses __attribute__((vector_size(N)) instead of intrinsics. Which makes it platform independent, but requires GCC or Clang for vectorization.

I don't plan to use GPU for computations, so I won't try cuFFT, clFFT, fbfft, GLFFT, etc.

First, a quick look at these projects:

Library License Since Language KLOC Comments
FFTW3 GPL or $ 1997 100+
MKL freeware 20?? ?
KFR GPL or $ 2016 C++14 ~20
FFTS MIT 2012 C 24
FFTE custom 20?? Fortran
muFFT MIT 2015 C 2.5
pffft BSD-like 2011 C 1.5
PGFFT 2-BSD 2019 C++11 1.0
KissFFT 3-BSD 2003 C 0.7+1.1 1.1 for tools/
meow_fft 0-BSD 2017 C 1.9 single header
pocketfft 3-BSD 2010? C++ 2.2 single header

Note: pocketfft was originally in C, but now the repository has a cpp branch and I'm migrating my benchmarks to it.

When I was looking for a fast JSON parser all the candidates were in C++, but here it's mostly C.

Selected features

I'm primarily after 3D complex-to-real and real-to-complex transforms. For me, radices 2 and 3 are a must, 5 is useful, 7+ could also be useful.

r-N means radix-N (radix-4 and 8 are supported anyway as 2^N).
"++" in the "prime" column means the Bluestein's algorithm.
"+/-" for radix-7 means it's only for complex-to-complex transform.
"s" and "d" denote single- and double-precision data.

Library r-3 r-4 r-5 r-7 r-8 prime 2D 3D s d
FFTW3 + + + + + ++ + + + +
MKL + + + + + +? + + + +
KFR + + + + + - - - + +
FFTS - + - - + ++ + + + +
FFTE + + + - + - + + - +
muFFT - + - - + - + - + -
pffft + + + - - - - - + -
PGFFT - - - - - ++ - - - +
KissFFT + + + - - + + + + +
meow_fft + + + - + + - - + -
pocketfft + + + +/- - ++ + + + +

Additionally, for pffft compiled with enabled SIMD the fft size must be a multiple of 16 for complex FFTs and 32 for real FFTs.

(let me know if I got anything wrong)

Preleminary benchmark

I run all the benchmarks here on i7-5600U CPU.

Just to get an idea, I checked the speed of popular Python libraries (the underlying FFT implementations are in C/C++/Fortran). I used only two 3D array sizes, timing forward+inverse 3D complex-to-complex FFT. Here are results from the preliminary.py script on my laptop (numpy and mkl are the same code before and after pip install mkl-fft):

lib   120x128x96 416x256x416
numpy    0.196      8.742
mkl      0.009      0.504
scipy    0.106      7.091
pyfftw   0.060      4.442

This is before NumPy switched to PocketFFT. NumPy will use internally PocketFFT from version 1.17, which is not released yet when I'm writing it.

(Update: I'm not planning on updating the results, but it's worth noting that SciPy also switched to PocketFFT in version 1.4.0. And added module scipy.fft with different API than the old scipy.fftpack. While NumPy is using PocketFFT in C, SciPy adopted newer version in templated C++.)

MKL is here as fast as in the native benchmark below (3d.cpp) while other libraries are slower than the slowest FFT run from C++.

Binary size

FFTW3 is a couple MB.
PocketFFT (C version) and muFFT are about 80kB. PocketFFT has more butterflies but muFFT has each in four versions (no-SIMD, SSE, SSE3 and AVX).
pffft and meow_fft are about 32kB. pffft has also four versions (no-SIMD, SSE1, AltiVec and NEON), but only one is compiled.
KissFFT (1D complex-to-complex) is only about 20kB. PGFFT – a few kB more.

1D performance

I'm benchmarking primarily lightweight libraries, and FFTW as the reference point. All the benchmarks on this page are:

Run on (4 X 3200 MHz CPU s)
CPU Caches:
  L1 Data 32K (x2)
  L1 Instruction 32K (x2)
  L2 Unified 256K (x2)
  L3 Unified 4096K (x1)

complex-to-complex (from running 1d.cpp compiled with GCC8 -O3)

                   n=256      n=384     n=480     n=512
fftw3 est.         321 ns     499 ns   1538 ns    663 ns
fftw3 meas.        274 ns     443 ns    883 ns    588 ns
mufft              325 ns     n/a        n/a      719 ns
pffft              585 ns    1014 ns   1329 ns   1255 ns
fftw3 est. NS     1826 ns    3254 ns   4776 ns   4095 ns
fftw3 meas. NS    1699 ns    2748 ns   3855 ns   3832 ns
mufft NS          1784 ns     n/a        n/a     4024 ns
pffft NS          1768 ns    2907 ns   4070 ns   3792 ns
pocketfft         1690 ns    3035 ns   3633 ns   4009 ns
meow_fft          2120 ns    4718 ns   5745 ns   4342 ns
kissfft           2536 ns    4929 ns   6030 ns   6553 ns

NS = disabled SIMD

I tested libraries with disabled SIMD (vectorization) because I plan to use FFT in WebAssembly which does not support SIMD instructions yet.

To a first approximation, SSE1 gives 3x speedup, AVX -- 6x.

Notes:

  • I didn't compile FFTW3, I used binaries from Ubuntu 18.04
  • -ffast-math doesn't seem to make a significant difference
  • When using Clang 8 instead of GCC, PocketFFT is ~12% faster.
  • All libraries are tested with single-precision numbers, except for PocketFFT (C version) which supports only double-precision.

I'm yet to check the accuracy of results.

plan / setup (plan1d.cpp)

Out of curiosity, I also checked how long it takes to generate a plan:

                  n=480       n=512
fftw3 est.       17871 ns     9693 ns
fftw3 meas.      31463 ns    25610 ns
mufft              n/a       17103 ns
pffft            12763 ns    13730 ns
pocketfft         1267 ns     1274 ns
meow_fft         15092 ns    13878 ns
kissfft          15586 ns    15993 ns

PocketFFT has indeed very fast plan generation.

real-to-complex (1d-r.cpp)

                   n=480       n=512
fftw3 est.         766 ns      814 ns
fftw3 meas.        718 ns      681 ns
mufft              n/a         511 ns
pffft              634 ns      597 ns
fftw3 est. NS     2442 ns     1921 ns
fftw3 meas. NS    1812 ns     1735 ns
mufft NS           n/a        2474 ns
pffft NS          2025 ns     1963 ns
pocketfft         2123 ns     2034 ns
meow_fftt         3591 ns     2660 ns
kissfft           3140 ns     2985 ns

NS = disabled SIMD

Notes:

  • The output from different libraries is ordered differently.
  • For small sizes (such as the ones above) R2C in FFTW (with SIMD) can be slower than C2C.

2D performance

complex-to-complex (2d.cpp)

               256x256    480x480
fftw3 est.     1197 us    3002 us
fftw3 meas.     306 us    1497 us
mufft           259 us      n/a
pocketfft       543 us    2270 us
fftw3 est. NS  1559 us    5582 us
fftw3 meas. NS 1033 us    4536 us
mufft NS       1092 us      n/a
kissfft        1583 us    5766 us

Here, PocketFFT is compiled with SSE1 support only. It is faster when compiled with AVX support. I haven't tried AVX-512.

3D performance

Here I also tried Intel MKL 2019 through its FFTW interface. No changes in the source code, only the linking command needs to be modified.

complex-to-complex (3d.cpp)

               128x128x320  256x256x256   416x256x416
MKL               38 ms        155 ms        492 ms
fftw3 est.        41 ms        730 ms       1860 ms
fftw3 meas.       39 ms        162 ms        727 ms
pocketfft         79 ms        264 ms        939 ms
fftw3 est. NS    253 ms        987 ms       2016 ms
fftw3 meas. NS   125 ms        443 ms       1476 ms
kissfft          216 ms        785 ms       4235 ms

real-to-complex (3d-r.cpp)

             128x128x320   256x256x256    416x256x416  90x128x120
MKL              17 ms         61 ms         185 ms        4 ms
fftw3 est.       28 ms        219 ms         605 ms       10 ms
fftw3 meas.      27 ms         98 ms         336 ms        7 ms
pocketfft AVX    30 ms         97 ms         311 ms        8 ms
pocketfft SSE    38 ms        126 ms         393 ms       10 ms
fftw3 est. NS    88 ms        285 ms         770 ms       19 ms
fftw3 meas. NS   62 ms        206 ms         715 ms       15 ms
kissfft         112 ms        436 ms        2078 ms       27 ms

PocketFFT compiled with AVX support is as fast as FFTW3.

matrix transpose (transpose.cpp)

Out of curiosity, I've also checked how long it takes to transpose a 3D matrix of type complex<float>. Only the last transpose is in-place (and it is also tiled).

            256x256x256
assign          22 ms
naive zyx      204 ms
naive xzy       89 ms
naive yxz       25 ms
naive zxy       90 ms
naive yzx      202 ms
tiled zxy       49 ms
in-place zxy    91 ms

Summary

For my project PocketFFT has the best trade-off between the size, features and performance.

I considered FFTW as a compile-time alternative, but I'd need to change how my data is ordered. The c2r transform in FFTW requires data contiguous in the halved direction. Simply transposing the data before FFT would likely cancel out any performance benefit from using FFTW.

Update: I've been using PocketFFT for almost a year now. I use the cpp branch. It's a perfect fit for my needs.