Conversation
Twiddle factors were regenerated on every execution: every kernel seeded Singleton's recurrence with a sincospi call (per output row of the O(n^2) DFT leaf, per j1 in the composite step, per level of the radix-4/3 kernels), and fft_bluestein! allocated three pad-length buffers and recomputed the chirp and its FFT on every call. CallGraph now carries, per node, a twiddle table in the layout its kernel reads sequentially (DFT: w^k; composite: the (j1, k2) block; radix-4/3: per-level interleaved triplets/pairs addressed by a flat offset), a BluesteinScratch (chirp, its pre-scaled transform, work arrays, pow2 tables for the padded length) per Bluestein node, and the direction the tables were built for. All tables derive from one unit_roots table per node that evaluates sincospi on the first octant only when 8 | N. Planned execution is allocation-free for every size. Tables are correctly rounded, so the Float32 error no longer grows with n (~1.5 ulp at 2^22 instead of ~1000); the accuracy test grid is extended to 2^22. The old kernel signatures remain as wrappers that build tables on the fly.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #134 +/- ##
==========================================
- Coverage 98.80% 98.79% -0.01%
==========================================
Files 5 5
Lines 585 666 +81
==========================================
+ Hits 578 658 +80
- Misses 7 8 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Real-transform rows for the table in the description (same machine and settings; "before" is main; the 10 errors are the unsupported 3D rfft, as on main):
Largest slowdowns / speedups (planned execution, FFTA before → after; FFTW for reference):
234 matched cases; geometric-mean speedup 1.94×; 1 cases slower by >5%. |
|
Made a few patches for easy performance wins :) wh_patch1.patch
|
Item A of the plan in #130: twiddle factors and Bluestein data are computed once at plan time and stored in the
CallGraph, instead of being regenerated on every execution.Before, every kernel seeded Singleton's recurrence with
singleton_params(asincospi) — once per output row of the O(n²)DFTleaf, once perj1infft_composite!, and per recursion level of the radix-4/3 kernels — andfft_bluestein!allocated three pad-length buffers and recomputed the chirp and its FFT on every call. Forn = 5the trig calls were the entire cost of the transform; forn = 1000about 1 800sincospiper execution.Now
CallGraphgetstwiddles::Vector{Vector{T}}(one table per node),bluestein::Vector{BluesteinScratch{T}}+blue_index, anddir::Direction(tables are direction specific;fft!throws if called with the other direction — plans always have a fixed direction).src/callgraph.jl):DFTnodes storew^k; composite nodes store the(j1, k2)block in the order the kernel walks it; the radix-4 and radix-3 kernels store per-level interleaved(w^k, w^2k, w^3k)/(w^k, w^2k)triplets/pairs and address them with a flat offset passed down the recursion. All tables are derived from oneunit_rootstable per node, which evaluatessincospionly on the first octant when8 | N(so planning a 2^20 transform costs ~130k trig calls, 8 ms, not 1.4 M).BluesteinScratchholds the chirp, its transform (pre-scaled by1/pad_len), the two work arrays and the pow2 tables for the padded length; the convolution is written with forward transforms only (ifft(y) = conj(fft(conj(y)))/n) so a single table set suffices. Planned execution is now allocation-free for every size, including primes and composites with a Bluestein factor.fft_dft!(..., d::Direction)etc.) remain as thin wrappers that build the table on the fly, so direct callers (the test suite, and the usage suggested in README benchmark result cannot be reproduced #119) keep working.Float32error no longer grows withn: atn = 2^22the relative error vs aFloat64reference drops from ~1000 ulp to 1.5 ulp (and from 10–28 ulp to 1.2–2.2 ulp across the whole 2^16…2^22 and 3^9…3^11 grid — FFTW is at ~1.5 ulp) (the accuracy test grid is extended to 2^20 and 2^22).Cost: plan creation now builds the tables — ~26 µs at 4096, ~0.4 ms at 65536, ~8 ms at 2^20, ~70 ms at 2^22 (
ComplexF64); memory per plan grows by roughly one table ofnentries per node level. One-shotfft(x)(plan + execute) is still faster than before at every size in the sweep.Before/after (aarch64 Neoverse-N1, Julia 1.12.6,
benchmark/suite.jl, planned execution, single thread;FFTA/FFTWis vs FFTW 3.3.11ESTIMATE):Largest slowdowns / speedups (planned execution, FFTA before → after; FFTW for reference):
261 matched cases; geometric-mean speedup 1.94×; 0 cases slower by >5%.
(Real-transform rows to follow in a comment: the suite run mis-detected
mul!for real plans on this branch; fixed in #128.)Tests: new
test/twiddles.jlpins the table layouts and accuracy, the direction check, the on-the-fly wrappers and zero allocations for planned execution; existing suite passes unchanged apart from the extended accuracy grid.