Performance roadmap: closing the gap to FFTW (proposed PR sequence)
- Dominant language
- Julia
- Stars
- 74
- Forks
- 8
- Avg merge
- 13h 43m
- Merged PRs (30d)
- 1
Description
I ran a broad FFTA-vs-FFTW sweep (PR #128) and profiled where the time goes, with the aim of closing the gap on the sizes where FFTA is far behind. This issue is to agree on the plan before opening PRs, since a few of the items touch the plan/callgraph structure.
**Where FFTA stands today** (aarch64 Neoverse-N1, Julia 1.12.6, FFTW 3.3.11 single-threaded, `ComplexF64`, planned execution, FFTA / FFTW; full tables and plots in the PR):
| type | pow2 | smooth | prime | prime×small | 2D | 3D | batched dims=1 | batched dims=2 |
|:--|--:|--:|--:|--:|--:|--:|--:|--:|
| ComplexF64 fft | 2.6× (1.0–7.1) | 7.3× (2.6–14.7) | 5.8× (3.1–10.6) | 5.6× (3.4–11.4) | 5.2× (1.8–13.3) | 7.6× (3.5–15.1) | 3.1× (2.0–5.7) | 2.2× (1.2–5.8) |
| Float64 rfft | 3.3× (1.4–7.3) | 8.7× (3.1–25.3) | 6.3× (2.6–11.9) | 6.2× (3.6–16.7) | 11.8× (5.7–25.5) | unsupported | 4.9× (2.9–9.7) | 3.1× (1.4–10.3) |
(geomean and range of FFTA/FFTW execution time; `Float32` is 1.3–1.5× worse than this because FFTA gets no SIMD benefit; FFTW `MEASURE` is a further 2.2–2.8× faster than `ESTIMATE` at n ≥ 2^19.)
**What profiling says** (details in `benchmark/ANALYSIS.md` in #129)
1. Twiddles are recomputed on every execution: `singleton_params` (a `sincospi`) runs once per output row of the O(n²) `DFT` leaf, once per `j1` in `fft_composite!`, and per recursion level in the radix-4/3 kernels. For n = 5 that is the whole cost of the transform (172 ns vs 37 ns with a table vs 23 ns for FFTW's codelet); for n = 1000 it is ~1800 `sincospi` per execution.
2. Bluestein allocates three pad-length buffers and recomputes the chirp *and its FFT* on every call (n = 1009: 3 × 56 µs of pow2 FFTs + 23 µs of setup, FFTW: 54 µs total).
3. The pow2 kernel recurses down to 2/4-point base cases; a `@generated` straight-line 16/32/64-point base case runs at 1.0–1.6× FFTW and compiles in < 1 s.
4. Real plans only implement `*` (no `mul!`), 2D/odd `rfft` runs a full complex transform, `rfft` along `dims` goes through `mapslices` (10× FFTW).
5. ND transforms allocate their pencil buffers per call and copy contiguous pencils unnecessarily; no threading across pencils.
6. With FFTW.jl also loaded, `plan_rfft(::Vector{Float64}, ::Int)` is a method ambiguity (FFTA's `region::RegionTypes` vs FFTW's `StridedArray`).
**Proposed PRs, in order** (each with before/after numbers from the suite)
- [ ] A. Store per-node twiddle tables (and Bluestein chirp/scratch) in the `CallGraph` at plan time; kernels read from them. Adds a `Vector{Vector{T}}`-style field per node; no API change. Expected: 2–5× on sizes with factors ≥ 5 and on primes, ~10–20% on pow2/pow3.
- [ ] B. `mul!` for real plans and a zero-allocation `rfft`/`irfft` path; `dims` path for real transforms reusing `fft_along_dim!` instead of `mapslices`.
- [ ] C. Straight-line base cases for the pow2 kernel (`Val{16/32/64}`, `@generated`, gated on `T <: Union{ComplexF32,ComplexF64}` so generic element types keep the current path), and codelets / radix butterflies for 5 and 7 (ref #105).
- [ ] D. Plan-owned ND buffers, contiguous-pencil fast path, optional threading across pencils (`Threads.@threads` over columns with one workspace per task).
- [ ] E. Smooth-length Bluestein padding and retuned `DEFAULT_BLUESTEIN_CUTOFF`; Rader for primes with smooth n−1 (later).
- [ ] F. Resolve the ambiguity with FFTW.jl (e.g. drop the `::RegionTypes` annotation on the `AbstractFFTs` entry points and convert inside).
A is the one that changes the plan structure (twiddle storage per node) and is a prerequisite for C and E, so I'd like to hear whether you're happy with twiddle tables living in `CallGraph` (vs. e.g. a per-node `NamedTuple` or a separate `Vector` parallel to `nodes`) before I open it. Happy to adjust scope or order.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with benchmark/ANALYSIS.md from #129 and the profiling results in PR #128, then inspect the CallGraph, singleton_params, and the proposed PR sequence. The immediate outcome is maintainer agreement on where twiddle tables and Bluestein state should live and whether the proposed order and scope are accepted before implementation begins.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- performance
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100