Skip to content

Commit 5577675

Browse files
Enable AVX512 scheduled inverse FFT
1 parent fd42bca commit 5577675

3 files changed

Lines changed: 21 additions & 9 deletions

File tree

examples/benchmark.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ struct Result {
410410
double standard_ns = NAN;
411411
double native_f32_ns = NAN;
412412
double standard_f32_ns = NAN;
413+
double inverse_ns = NAN;
413414
double roundtrip_ns = NAN;
414415
double standard_over_fftw = NAN;
415416
double standard_f32_over_fftwf = NAN;
@@ -612,6 +613,13 @@ Result bench_one(std::size_t n, int forced_iters, FFTW* fftw, FFTWf* fftwf, Inte
612613
result.sink += standard_f32[(static_cast<std::size_t>(result.sink) * 17) % nb].re;
613614
});
614615

616+
plan.forward(original.data(), standard.data(), work.data(), scratch.data());
617+
result.inverse_ns = bench_ns(iters, [&] {
618+
standard[(static_cast<std::size_t>(result.sink) * 17) % nb].re += 1e-12;
619+
plan.inverse(standard.data(), inverse_out.data());
620+
result.sink += inverse_out[(static_cast<std::size_t>(result.sink) * 17) % n];
621+
});
622+
615623
input = original;
616624
result.roundtrip_ns = bench_ns(iters, [&] {
617625
input[static_cast<std::size_t>(result.sink) & (n - 1)] += 1e-12;
@@ -696,6 +704,7 @@ Result bench_one(std::size_t n, int forced_iters, FFTW* fftw, FFTWf* fftwf, Inte
696704
print_ns(result.standard_ns);
697705
print_ns(result.native_f32_ns);
698706
print_ns(result.standard_f32_ns);
707+
print_ns(result.inverse_ns);
699708
print_ns(result.roundtrip_ns);
700709

701710
if (std::isnan(result.standard_over_fftw)) {
@@ -827,7 +836,7 @@ int main(int argc, char** argv) {
827836
std::printf("PFFFT disabled; compile with -DBFFT_WITH_PFFFT and link pffft to enable.\n");
828837
#endif
829838

830-
std::printf("%8s %8s %11s %11s %11s %11s %11s %11s %11s %11s %11s %11s %8s %8s %8s %8s %8s %8s %s\n",
839+
std::printf("%8s %8s %11s %11s %11s %11s %11s %11s %11s %11s %11s %11s %11s %8s %8s %8s %8s %8s %8s %s\n",
831840
"N",
832841
"iters",
833842
"FFTW_ns",
@@ -839,6 +848,7 @@ int main(int argc, char** argv) {
839848
"Std_ns",
840849
"F32Nat_ns",
841850
"F32Std_ns",
851+
"Inv_ns",
842852
"RT_ns",
843853
"S/F64",
844854
"F32/Ff",

src/detail/bruun_kernel.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4318,7 +4318,10 @@ class RFFT {
43184318
// Fast scheduled residue inverse: exact reverse of forward_residues_recursive.
43194319
// Requires N >= 64.
43204320
void inverse_residues_recursive(double* RESTRICT v) const {
4321-
#if BRUUN_LEVEL == 1
4321+
#if BRUUN_LEVEL <= 3
4322+
// Wide x86 uses the same flat inverse schedule as 128-bit SIMD. The
4323+
// fused d4 inverse codelet keeps the q==8 leaves register-resident, so
4324+
// the schedule no longer pays an extra store/reload penalty at the leaf.
43224325
run_inv_residue_schedule(v);
43234326
#else
43244327
residue_spine_tail_inv(v);

src/detail/inverse_acceleration_notes.txt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ Why this is first:
2020

2121
ISA notes:
2222
- SSE/NEON benefit immediately from removing recursive calls and from the existing 128-bit `norm_q_inv` loop.
23-
- AVX2 keeps the existing recursive path for now because the first scheduled attempt did not beat it on this host.
24-
- AVX-512 is treated the same as AVX2 until the inverse d4 q==8 fusion is available; the schedule infrastructure is present, but not enabled for wide-vector builds yet.
23+
- AVX2 and AVX-512 now use the scheduled inverse as well. After the double-precision d4 inverse fusion, q==8 leaves stay register-resident in both recursive and scheduled runners, and direct inverse benchmarking on wide x86 hosts showed the schedule paying for its flat dispatch.
2524

2625
Completed:
2726
1. [done] Fused inverse d4 codelet for f32 q==8 (`codelet_d4_inv_f32`): two inverse d3 leaves + inverse q=8 norm in one function, values stay in registers. Eliminates 8 loads + 8 stores per q==8 block. Wired into both the schedule runner and the recursive path.
@@ -32,9 +31,9 @@ Completed:
3231
Follow-up candidates:
3332
2. Fuse native/standard gather with the inverse binomial tail to remove one complete pass over N for inverse-from-complex APIs.
3433
3. Add f32/scalar lane-doubling equivalents where the inverse leaf still falls back to generic inverse-norm ladders.
35-
6. Re-evaluate enabling the scheduled inverse for AVX2 now that the d4 fusion is available — the register-residency win may now overcome flat-dispatch overhead.
3634

37-
SSE-first gate:
38-
- Initial AVX2 timing showed the flat inverse schedule is not yet a win on this host, likely because AVX2 already has the recursive leaf path inlined into large vector kernels while the schedule adds switch pressure.
39-
- The scheduled inverse is therefore enabled first for 128-bit SSE/NEON builds, where removing recursive calls is the current concrete win.
40-
- AVX2 and AVX-512 keep the existing recursive path until the d4 fusion is ported to those ISAs and benchmarked.
35+
Wide-x86 schedule gate:
36+
- Rechecked the scheduled inverse on Intel Xeon Platinum 8370C with `-O3 -mavx2 -mfma -mno-avx512*`.
37+
- Rechecked the AVX-512 scheduled inverse on the same host with `-O3 -march=native`.
38+
- The benchmark now prints a direct `Inv_ns` column so inverse-only changes are visible instead of inferred from round-trip timing.
39+
- The d4 fusion was correctness-checked on AVX2 and AVX-512 and is fast enough to enable the flat inverse schedule for BRUUN_LEVEL 2 and 3.

0 commit comments

Comments
 (0)