Skip to content

Commit 2cff643

Browse files
Improve native f32 output locality
1 parent 39e7d3b commit 2cff643

2 files changed

Lines changed: 41 additions & 4 deletions

File tree

src/ASSEMBLY_NOTES.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,30 @@ Locality measurements after the converter change
9999
- convert32 hot_ns 1796649.7, ring_ns 2212638.0, ring/hot 1.232 after the store-order change.
100100
- build/examples/benchmark 4096 3000 after the converter change: Native_ns 3962.2, Std_ns 5200.2, F32Nat_ns 2961.7, F32Std_ns 3019.6.
101101
- build/examples/benchmark 65536 500 after the converter change: Native_ns 124034.0, Std_ns 152776.9, F32Nat_ns 69347.7, F32Std_ns 79781.1.
102+
103+
Native f32 locality follow-up, 2026-06-12
104+
105+
Change
106+
- forward_native_f32 now emits heapopt native order by iterating native output positions and reading NATIVE_LEAF[pos] from the residue work buffer.
107+
- This makes the public native spectrum write stream linear. The unavoidable permutation moves to reads from the already-hot residue buffer, which is generally cheaper than scattered complex output stores.
108+
- Small N without heapopt still uses the old IDX loop.
109+
110+
Assembly check
111+
- On arm64 Apple clang, build/src/bfft.s shows the native f32 output loop loading two NATIVE_LEAF indices with ldpsw, gathering two residue pairs from work, and writing two adjacent complex_f32_t results with stp while advancing the output pointer.
112+
- This is the locality shape we want from a generic design perspective: sequential destination pressure, compact index stream, no architecture-specific scatter primitive.
113+
114+
Locality measurements after native f32 store-order change
115+
- build/examples/locality_probe 4096 3000 64:
116+
- native32 hot_ns 3837.4, ring_ns 3330.0, ring/hot 0.868.
117+
- standard32 hot_ns 3004.2, ring_ns 3936.0, ring/hot 1.310.
118+
- build/examples/locality_probe 65536 600 64:
119+
- native32 hot_ns 63839.8, ring_ns 67469.0, ring/hot 1.057.
120+
- standard32 hot_ns 95573.3, ring_ns 87344.9, ring/hot 0.914.
121+
- build/examples/locality_probe 1048576 80 128:
122+
- native32 hot_ns 1269024.5, ring_ns 1319281.2, ring/hot 1.040.
123+
- standard32 hot_ns 1734335.4, ring_ns 1712309.4, ring/hot 0.987.
124+
125+
Benchmark checks after native f32 store-order change
126+
- build/examples/benchmark 4096 3000: F32Nat_ns 3527.7, F32Std_ns 3482.8.
127+
- build/examples/benchmark 65536 500: F32Nat_ns 63909.5, F32Std_ns 77834.9.
128+
- build/examples/benchmark 1048576 80: F32Nat_ns 1242196.4, F32Std_ns 1571345.8.

src/detail/bruun_kernel.hpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,11 +1957,21 @@ class RFFT {
19571957
X[N / 2].re = work[0] - work[1];
19581958
X[N / 2].im = 0.0f;
19591959

1960-
// Native order is heapopt OUTIDX order for N >= 32 and plain FFTW bin
1961-
// order below that, matching the double engine and the f32 converters.
1962-
const int* RESTRICT out_idx = (N >= 32) ? OUTIDX.data() : IDX.data();
1960+
#if defined(BRUUN_HEAPOPT_SPECTRUM_ORDER)
1961+
if (N >= 32) {
1962+
// Native heapopt order is a layout permutation; keep the output
1963+
// stream linear and pay the permutation on residue reads.
1964+
const int* RESTRICT native_leaf = NATIVE_LEAF.data();
1965+
for (int pos = 1; pos < N / 2; ++pos) {
1966+
const int m = native_leaf[pos];
1967+
X[pos].re = work[2*m];
1968+
X[pos].im = -work[2*m + 1];
1969+
}
1970+
return;
1971+
}
1972+
#endif
19631973
for (int m = 1; m < N / 2; ++m) {
1964-
const int k = out_idx[m];
1974+
const int k = IDX[m];
19651975
X[k].re = work[2*m];
19661976
X[k].im = -work[2*m + 1];
19671977
}

0 commit comments

Comments
 (0)