Skip to content

Commit 7cb2b9e

Browse files
Merge pull request #43 from falseywinchnet/c93rxr-main/implement-avx2-pathway-for-odd-fft-transform
Add AVX2 forward-combine path to BODFT kernel
2 parents d9507ef + e109ec1 commit 7cb2b9e

3 files changed

Lines changed: 117 additions & 2 deletions

File tree

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ SFDR_PROBE := $(BUILD_DIR)/tests/bfft_fftw_sfdr_probe
6666
BH7_PROBE := $(BUILD_DIR)/tests/bfft_fftw_sfdr_bh7_probe
6767
LIBRARY_COMPARE_PROBE := $(BUILD_DIR)/tests/bfft_library_compare_probe
6868
AVX2_ASM := $(BUILD_DIR)/src/bfft_avx2.s
69+
BODFT_AVX2_ASM := $(BUILD_DIR)/src/bodft_avx2.s
6970
SSE2_ASM := $(BUILD_DIR)/src/bfft_sse2.s
7071
ASM_OUTPUTS :=
7172
ifneq ($(AVX2_FLAGS),)
72-
ASM_OUTPUTS += $(AVX2_ASM)
73+
ASM_OUTPUTS += $(AVX2_ASM) $(BODFT_AVX2_ASM)
7374
endif
7475
ifneq ($(SSE2_FLAGS),)
7576
ASM_OUTPUTS += $(SSE2_ASM)
@@ -109,6 +110,9 @@ asm-check: $(ASM_OUTPUTS)
109110
$(AVX2_ASM): $(SRC) include/bfft/bfft.h src/detail/bruun_kernel.hpp | $(BUILD_DIR)
110111
$(CXX) $(LIB_CPPFLAGS) $(CXXFLAGS) $(AVX2_FLAGS) -S -fverbose-asm $< -o $@
111112

113+
$(BODFT_AVX2_ASM): $(BODFT_SRC) include/bfft/bodft.h include/bfft/bfft.h src/detail/bodft_kernel.hpp src/detail/bruun_kernel.hpp | $(BUILD_DIR)
114+
$(CXX) $(LIB_CPPFLAGS) $(CXXFLAGS) $(AVX2_FLAGS) -S -fverbose-asm $< -o $@
115+
112116
$(SSE2_ASM): $(SRC) include/bfft/bfft.h src/detail/bruun_kernel.hpp | $(BUILD_DIR)
113117
$(CXX) $(LIB_CPPFLAGS) $(CXXFLAGS) $(SSE2_FLAGS) -S -fverbose-asm $< -o $@
114118

src/detail/bodft_avx2_notes.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# BODFT AVX2 pathway notes
2+
3+
## 2026-06-23
4+
5+
- Added a 256-bit AVX2/FMA double-precision forward combine path for the odd-frequency transform.
6+
- The AVX2 code handles four paired radix-4 positions per iteration, twice the double-lane width of the SSE2/NEON path.
7+
- The path uses structure-of-arrays lane registers for child spectra and twiddle tables, performs the three child twiddle complex multiplies with FMA instructions, then stores the forward and conjugate-partner outputs through one packing helper.
8+
- Kept the existing 128-bit SIMD double path as the fallback tail and non-AVX2 implementation. Float remains on the portable 128-bit SIMD path because its current path already handles four positions per iteration and needs a separate eight-position AVX2 packing pass to be worthwhile.
9+
- Added a Makefile assembly target for `src/bodft.cpp` so `make asm-check` emits both the regular BFFT AVX2 assembly and the BODFT AVX2 assembly.
10+
11+
## Validation notes
12+
13+
- Correctness was checked with `make test BUILD_DIR=build-avx2 CXXWARNFLAGS='-Wall -Wextra -Wpedantic -Werror'`.
14+
- BODFT-specific correctness and timing were checked with `make build-avx2/examples/bodft_benchmark BUILD_DIR=build-avx2 && build-avx2/examples/bodft_benchmark`.
15+
- Assembly was checked with `make asm-check BUILD_DIR=build-avx2` and by scanning `build-avx2/src/bodft_avx2.s` for AVX/FMA instructions.
16+
17+
## Performance observations from this container
18+
19+
- Backend reported by the benchmark: `avx2-fma-256`.
20+
- Double forward timings from the AVX2 path were approximately 0.83 us at N=256, 3.88 us at N=1024, 19.2 us at N=4096, 458 us at N=65536, and 18.3 ms at N=1048576.
21+
- Float forward timings remained on the existing 128-bit path and were approximately 0.71 us at N=256, 3.13 us at N=1024, 14.7 us at N=4096, 313 us at N=65536, and 11.9 ms at N=1048576.
22+
- The next performance task is an eight-position AVX2 float combine and, after that, an AVX2 inverse combine if inverse parity becomes a requirement.
23+
- For a same-container point comparison, `build-avx2/examples/benchmark 65536 200` measured the regular BFFT native double path at about 217 us for N=65536; the BODFT double forward path measured about 458 us for N=65536 in the broader BODFT benchmark. The gap is now concentrated in BODFT's transform-specific data movement/scratch schedule and unvectorized inverse rather than missing AVX2 forward-combine code.

src/detail/bodft_kernel.hpp

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,81 @@ static inline CT cmuli(CT a) { return CT{-a.im, a.re}; }
9090
# endif
9191
#endif
9292

93+
94+
#if BRUUN_LEVEL >= 2
95+
static inline void bodft_uzp4_pd(const complex_t* RESTRICT src, int k,
96+
__m256d& re, __m256d& im) {
97+
const __m256d a = _mm256_loadu_pd(&src[k].re);
98+
const __m256d b = _mm256_loadu_pd(&src[k + 2].re);
99+
const __m256d lo = _mm256_unpacklo_pd(a, b);
100+
const __m256d hi = _mm256_unpackhi_pd(a, b);
101+
re = _mm256_permute4x64_pd(lo, 0xD8);
102+
im = _mm256_permute4x64_pd(hi, 0xD8);
103+
}
104+
105+
static inline void bodft_store4_pd(complex_t* RESTRICT dst, int k,
106+
__m256d re, __m256d im) {
107+
const __m256d lo = _mm256_unpacklo_pd(re, im);
108+
const __m256d hi = _mm256_unpackhi_pd(re, im);
109+
_mm256_storeu_pd(&dst[k].re, _mm256_permute2f128_pd(lo, hi, 0x20));
110+
_mm256_storeu_pd(&dst[k + 2].re, _mm256_permute2f128_pd(lo, hi, 0x31));
111+
}
112+
113+
static inline __m256d bodft_rev4_pd(__m256d v) {
114+
return _mm256_permute4x64_pd(v, 0x1B);
115+
}
116+
117+
static inline int bodft_combine_fwd_avx2_f64(const complex_t* RESTRICT tab,
118+
const complex_t* RESTRICT tab2,
119+
const complex_t* RESTRICT tab3,
120+
const complex_t* RESTRICT c0,
121+
const complex_t* RESTRICT c1,
122+
const complex_t* RESTRICT c2,
123+
const complex_t* RESTRICT c3,
124+
complex_t* RESTRICT out,
125+
int M, int half) {
126+
const __m256d zero = _mm256_setzero_pd();
127+
int k = 0;
128+
for (; k + 4 <= half; k += 4) {
129+
__m256d tre, tim, t2re, t2im, t3re, t3im;
130+
__m256d c0re, c0im, c1re, c1im, c2re, c2im, c3re, c3im;
131+
bodft_uzp4_pd(tab, k, tre, tim);
132+
bodft_uzp4_pd(tab2, k, t2re, t2im);
133+
bodft_uzp4_pd(tab3, k, t3re, t3im);
134+
bodft_uzp4_pd(c0, k, c0re, c0im);
135+
bodft_uzp4_pd(c1, k, c1re, c1im);
136+
bodft_uzp4_pd(c2, k, c2re, c2im);
137+
bodft_uzp4_pd(c3, k, c3re, c3im);
138+
139+
const __m256d b0re = c0re, b0im = c0im;
140+
const __m256d b1re = _mm256_fmsub_pd(c1re, tre, _mm256_mul_pd(c1im, tim));
141+
const __m256d b1im = _mm256_fmadd_pd(c1re, tim, _mm256_mul_pd(c1im, tre));
142+
const __m256d b2re = _mm256_fmsub_pd(c2re, t2re, _mm256_mul_pd(c2im, t2im));
143+
const __m256d b2im = _mm256_fmadd_pd(c2re, t2im, _mm256_mul_pd(c2im, t2re));
144+
const __m256d b3re = _mm256_fmsub_pd(c3re, t3re, _mm256_mul_pd(c3im, t3im));
145+
const __m256d b3im = _mm256_fmadd_pd(c3re, t3im, _mm256_mul_pd(c3im, t3re));
146+
147+
const __m256d e0re = _mm256_add_pd(b0re, b2re), e0im = _mm256_add_pd(b0im, b2im);
148+
const __m256d e1re = _mm256_sub_pd(b0re, b2re), e1im = _mm256_sub_pd(b0im, b2im);
149+
const __m256d o0re = _mm256_add_pd(b1re, b3re), o0im = _mm256_add_pd(b1im, b3im);
150+
const __m256d o1re = _mm256_sub_pd(b1re, b3re), o1im = _mm256_sub_pd(b1im, b3im);
151+
152+
const __m256d yk_re = _mm256_add_pd(e0re, o0re), yk_im = _mm256_add_pd(e0im, o0im);
153+
const __m256d ykM_re = _mm256_add_pd(e1re, o1im), ykM_im = _mm256_sub_pd(e1im, o1re);
154+
const __m256d ykp_re = _mm256_sub_pd(e1re, o1im);
155+
const __m256d ykp_im = _mm256_sub_pd(zero, _mm256_add_pd(e1im, o1re));
156+
const __m256d ykpM_re = _mm256_sub_pd(e0re, o0re);
157+
const __m256d ykpM_im = _mm256_sub_pd(o0im, e0im);
158+
159+
bodft_store4_pd(out, k, yk_re, yk_im);
160+
bodft_store4_pd(out, k + M, ykM_re, ykM_im);
161+
bodft_store4_pd(out, M - 4 - k, bodft_rev4_pd(ykp_re), bodft_rev4_pd(ykp_im));
162+
bodft_store4_pd(out, M - 4 - k + M, bodft_rev4_pd(ykpM_re), bodft_rev4_pd(ykpM_im));
163+
}
164+
return k;
165+
}
166+
#endif
167+
93168
// ---------------------------------------------------------------------------
94169
// Forward double combine, 128-bit SoA SIMD specialization. Two output positions
95170
// k, k+1 per iteration; children and twiddles transpose into real/imag lane
@@ -234,9 +309,22 @@ static inline void combine_fwd(const CT* RESTRICT tab, const CT* RESTRICT tab2,
234309
const CT* RESTRICT c3, CT* RESTRICT out,
235310
int M, int half) {
236311
int k = 0;
312+
#if BRUUN_LEVEL >= 2
313+
if constexpr (sizeof(RT) == 8) {
314+
k = bodft_combine_fwd_avx2_f64(
315+
reinterpret_cast<const complex_t*>(tab),
316+
reinterpret_cast<const complex_t*>(tab2),
317+
reinterpret_cast<const complex_t*>(tab3),
318+
reinterpret_cast<const complex_t*>(c0),
319+
reinterpret_cast<const complex_t*>(c1),
320+
reinterpret_cast<const complex_t*>(c2),
321+
reinterpret_cast<const complex_t*>(c3),
322+
reinterpret_cast<complex_t*>(out), M, half);
323+
}
324+
#endif
237325
#if BRUUN_LEVEL >= 1
238326
if constexpr (sizeof(RT) == 8) {
239-
k = bodft_combine_fwd_simd_f64(
327+
if (k == 0) k = bodft_combine_fwd_simd_f64(
240328
reinterpret_cast<const complex_t*>(tab),
241329
reinterpret_cast<const complex_t*>(tab2),
242330
reinterpret_cast<const complex_t*>(tab3),

0 commit comments

Comments
 (0)