Development, benchmarking, and analysis repository for zldsp::fft. To use the library in a project, see zldsp_fft.
Requirements: Clang (AppleClang 16+ or LLVM/Clang 17+), CMake (3.25+), and Ninja. On Windows, run from a Visual Studio developer shell.
git clone https://github.com/ZL-Audio/zldsp_fft_develop
cd zldsp_fft_develop
git submodule update --initRun all benchmarks via a single CLI (1 <= n0 <= n1 <= 30, FFT size N = 2^order):
python3 benchmark/run.py <accuracy|throughput|compare> <cfft|rfft> <n0> <n1> [options]--double: Usedoubleprecision (default:float).--avx2/--sse2: Select x86 SIMD target (default: SSE4.2 on x86; ARM uses NEON).--apis: Test allzldsp::fftlayouts and APIs (AoS/SoA,forward_sqr_mag, RFFTconvolve).--repeats <k>: Repetitions for random accuracy signals (3), throughput timings (3), or paired comparisons (6).--artifacts-dir <dir>: Build/temp directory (default:<repo>/build_fft, or system temp directory forcompare).--json <file>: Write structured results to JSON.
Default library accuracy requires Python 3.10+ and python-flint (--apis does not):
python3 -m pip install --only-binary=:all: -r benchmark/requirements-accuracy.txt
python3 benchmark/run.py accuracy cfft 2 20
python3 benchmark/run.py accuracy rfft 2 20 --apis --double
python3 -m unittest discover -s benchmark -p test_flint_reference.py -v- Library accuracy (default): Streams exact
binary64samples fromfftw3_estimate,kfr, andzldspto compare against arbitrary-precision FLINT/Arb DFTs (192/384/768-bit ball arithmetic with relative uncertainty<= min(2^-80, threshold/1024)), and checks normalized round trips (1/Nfor CFFT;2/NforzldspRFFT since backward scales byN/2). - API consistency (
--apis): Validates allzldspAoS/SoA layouts,forward_sqr_mag, andconvolveoverloads against the AoS reference path and small direct DFTs. - Metrics: Reports worst relative RMSE and max absolute error (conservative upper bounds including reference uncertainty in library mode) across deterministic and random signals; fails above
--threshold(2e-5forfloat,2e-12fordouble).
python3 benchmark/run.py throughput cfft 5 20
python3 benchmark/run.py throughput rfft 5 20 --libraries zldsp kfr pffft vdsp
python3 benchmark/run.py throughput rfft 5 20 --standard-libraries
python3 benchmark/run.py throughput cfft 5 20 --all-libraries --double
python3 benchmark/run.py throughput rfft 5 20 --apis- Targets (default:
zldsp):--libraries <lib ...>,--standard-libraries(zldsp,kfr,pffft, plus installedvdsp,ipp,armpl,armpl_estimate),--all-libraries(addsfftw3,fftw3_estimate), or--apis(allzldsplayouts,forward_sqr_mag, and fused vs. separateconvolve). - Measurement: Reuses plans and buffers to time only transforms (using native split/unordered formats for vDSP and PFFFT), runs all selected targets at each order before advancing, and frees buffers between orders.
- Timing & builds: Per-repetition duration scales exponentially from
--min-time0(0.5s atn0) to--min-time1(5s atn1), or fixed via--min-time <s>. Use--build-onlyor--skip-buildto decouple building from timing. - Metrics: Reports nominal MFLOPS from median single-call times across
--repeatsruns (5*N*log2(N)for CFFT,2.5*N*log2(N)for RFFT).
python3 benchmark/run.py compare cfft 5 20 --repeats 6
python3 benchmark/run.py compare rfft 5 20 --apis --current HEAD --previous HEAD^Alternates --current (default HEAD) and --previous (default HEAD^) across --repeats paired runs, reporting median times, speedup ratios (> 1 is faster), percentage changes, and MAD.
zldsp, fftw3, fftw3_estimate, kfr, pffft, vdsp, ipp, armpl, armpl_estimate (fftw3 and armpl use FFTW_MEASURE; *_estimate use FFTW_ESTIMATE). Set ARMPL_DIR or IPP_ROOT for non-default installations.
zldsp::fft is a C++20 header-only library built on Google Highway (hwy), supporting fixed-width 128-bit and 256-bit SIMD targets:
- Static Dispatch: Compile for a single fixed SIMD target (
zldsp::fft::CFFT<F>,zldsp::fft::RFFT<F>) with direct calls. - Caller-owned Dynamic Dispatch: Compile multiple SIMD targets in a single translation unit via
<hwy/foreach_target.h>and dispatch at plan creation. Recommended for GCC and Clang (clang++/clang-cl). - Separately Compiled Dispatch: Compile each x86-64 SIMD backend (
SSE2,SSE4,AVX2) as a separate object library with dedicated ISA flags and dispatch at plan creation. Recommended for MSVC (and supported on GCC/Clang).
Important
Twiddle initialization relies on scoped pragmas to preserve intermediate IEEE 754 rounding. On Clang / AppleClang / clang-cl, do not use plain -ffp-contract=fast (or -ffast-math / /fp:fast without override), which can fuse operations in the LLVM backend despite local pragmas; use -ffp-contract=fast-honor-pragmas (or /clang:-ffp-contract=fast-honor-pragmas on clang-cl) instead. See tools/twiddle/README.md for details.
Include zldsp_fft_cfft.hpp / zldsp_fft_rfft.hpp, define HWY_COMPILE_ONLY_STATIC, and select the target ISA with compiler flags:
target_compile_definitions(my_static_target PRIVATE HWY_COMPILE_ONLY_STATIC)| SIMD Target | GCC/Clang | MSVC |
|---|---|---|
| SSE2 | -march=x86-64 |
no flag required |
| SSE4 | -march=x86-64-v2 -maes -mpclmul |
not supported |
| AVX2 | -march=x86-64-v3 -maes -mpclmul |
/arch:AVX2 |
| NEON | -march=armv8-a+simd |
/arch:armv8.0 |
See static_dispatch_caller.cpp for complete examples.
Re-include zldsp_fft_cfft-inl.hpp and zldsp_fft_rfft-inl.hpp with <hwy/foreach_target.h> in a wrapper translation unit to instantiate zldsp::fft::HWY_NAMESPACE::CFFT<F> / RFFT<F> per target and dispatch plan creation with HWY_DYNAMIC_DISPATCH.
Because zldsp::fft only supports 128-bit and 256-bit fixed-width SIMD, you must define HWY_DISABLED_TARGETS to disable unsupported targets (such as AVX-512 or SVE) and compile for your oldest supported baseline (e.g., -march=x86-64 for SSE2):
target_compile_definitions(my_dynamic_target PRIVATE "HWY_DISABLED_TARGETS=~(HWY_SSE2|HWY_AVX2)")See dynamic_dispatch_wrapper.hpp, dynamic_dispatch_wrapper.cpp, and dynamic_dispatch_caller.cpp.
Warning
MSVC may cause severe performance degradation when the dispatch target is AVX2 and the baseline is SSE2. Use Separately Compiled Dispatch for runtime dispatch with MSVC.
With Highway available as the hwy target, separate_dispatch.cmake compiles dynamic_dispatch_wrapper.cpp once per x86-64 backend with HWY_COMPILE_ONLY_STATIC and matching ISA flags, then links them into zldsp_fft_separate_dispatch:
set(ZLDSP_FFT_SEPARATE_TARGETS SSE2 SSE4 AVX2)
include(path/to/zldsp_fft_develop/examples/separate_dispatch.cmake)
target_link_libraries(my_separate_target PRIVATE zldsp_fft_separate_dispatch)- Backends: Choose from
SSE2,SSE4, andAVX2(SSE4is not supported by MSVC). IncludeSSE2for a baseline fallback; plan creation throwsstd::runtime_errorif no compiled backend supports the CPU. - Build settings: The helper sets ISA flags only on the per-backend object libraries; your application,
hwy, and the dispatcher inherit your own baseline, optimization, PIC, and IPO settings. - Integration: Link
zldsp_fft_separate_dispatchinstead of compilingdynamic_dispatch_wrapper.cppdirectly. Seeseparate_dispatch_wrapper.cpp,dynamic_dispatch_wrapper.hpp, anddynamic_dispatch_caller.cpp.
zldsp_fft is licensed under the Apache-2.0 license (LICENSE.md). External submodules retain their respective licenses:
- FFTW3:
fftw3_impl/fftw3(GPL-2.0) - KFR:
kfr_impl/kfr(GPL-2.0) - PFFFT:
pffft_impl/pffft(BSD-like) - Google Benchmark:
google/benchmark(Apache-2.0) - Google Highway:
google/highway(Apache-2.0)