SweepFFT:面向缓存外快速傅里叶变换的最小遍历算法
SweepFFT: Pass-Minimal Algorithms for Out-of-Cache Fast Fourier Transforms
SweepFFT 提出面向缓存外 FFT 的最小遍历(pass-minimal)算法,在五台机器上比 MKL 快 1.15–1.96×、比 FFTW 快 1.27–3.52×,所有主要端点 p_Holm = 0.002。
SweepFFT:面向缓存外快速傅里叶变换的通过次数最少算法。
近期工作证明了在多带图灵机上整数乘法和 DFT 可在 O(n (log n)^{1-κ}) 内完成:
- OpenAI 的论文手稿,Integer multiplication below n log n(openai/math);
- 后续的 CrocSwap/integer-mult-bounds。
这一增益本身,在任何能放进 RAM 的规模下都无法测量。但证明所优化的代价模型,即对磁带的通过次数,与对 DRAM 的扫描紧密对应,而这正是限制大规模 FFT 的因素。
在所有系统共有的最大变换长度下,在头条协议下,SweepFFT 比 MKL 快 1.15–1.96 倍,比 FFTW 快 1.27–3.52 倍。这一优势取决于缓存占用、因式分解、规划和分页。在一个系统上进行的析因消融实验将 94.5–99.6% 的可解释缓存外对数运行时间变异归因于批处理、融合、二者的交互作用以及非临时存储。
完整报告:results/REPORT.md。
最终结果
五台机器:预注册端点
每台机器都用最终代码运行了完整测试套件(scripts/run_machine.sh <label> --all),包括来自 results/ENDPOINTS.md 的两项预注册研究。每项研究包含 10 个区块的全新固定进程,使用 2^22 – 2^26 上的几何平均值,并报告 95% 区块自助法置信区间。以下每个主要端点都支持 pmfft,p_Holm = 0.002。普通指调用方数组以普通方式分配(主要研究);大页指调用方数组位于 2 MB 页上。
| 机器 | 对比 FFTW,普通 | 对比 FFTW,大页 | 对比 MKL,普通 | 对比 MKL,大页 | 大型非二次幂对比 MKL |
|---|---|---|---|---|---|
| Intel i7-13700H(笔记本) | 1.18 [1.16, 1.20] | 1.45 | 1.03 [1.02, 1.04] | 1.13 | 2.29 |
| Intel Xeon Gold 6234(服务器,Cascade Lake) | 1.23 [1.20, 1.25] | 1.39 | 1.18 [1.15, 1.20] | 1.19 | 1.20 |
| AMD EPYC 7302(服务器,Zen 2) | 1.32 [1.30, 1.34] | 1.56 | 1.17 [1.16, 1.18] | 1.65 | 1.97 |
| AMD Ryzen 9 3900X(台式机,Zen 2) | 1.31 [1.30, 1.31] | 1.60 | 1.22 [1.22, 1.22] | 1.74 | 2.00 |
| AMD Ryzen 7 7800X3D(台式机,Zen 4,96 MB L3) | 1.23 [1.22, 1.24] | 1.45 | 1.28 [1.26, 1.30] | 1.15 | 2.08 |
- pmfft 在哪里落败:在缓存内(2^4 – 2^11),每台机器上的大多数规模下 FFTW 都更快。
- 分页效应因机器而异(REPORT §20–21)。它在 Zen 2 上最强,在 Xeon 上较小,而在 7800X3D 上对 MKL 则相反。
- 在任何机器上,单核下都没有重叠。数据移动和算术运算是相加而非重叠:在所有情况下它们的总和为完整时间的 0.88 – 1.00(REPORT §22)。
在你的机器上运行基准测试
要求:
- x86-64 上的 Linux,支持 AVX2 + FMA(Intel Haswell 或更新,AMD Zen 或更新);
- gcc/g++ ≥ 11、make、curl、git、python3 以及 uv;
- 约 2 GB 磁盘空间;
- 最大规模需要 16 GB 或更多 RAM(2^26 在规划时每个进程约需 8.5 GB;较小的机器会自动停在较小的规模)。
git clone https://github.com/dzako/sweepfft && cd sweepfft uv sync # Python env (pandas, numpy, ...) for the scripts scripts/setup_third_party.sh # download + build FFTW 3.3.10, MKL 2024.2.2 (wheels), ducc0 (~5 min) scripts/build.sh # build all benchmarks into build/ scripts/run_machine.sh my-box --quick # 5-minute smoke test -> results/machines/my-box-quick/ scripts/run_machine.sh <label> # full run (45-90 min) -> results/machines/<label>/ scripts/run_machine.sh <label> --all # every study below (~12-20 h)
完整运行测量什么
除非给出 --mt,否则一切都以单核运行,使用与报告相同的协议:
- 从 2^4 到 2^26 的二次幂;
- 光滑合数规模;
- 素数,包括精确模式和
fast=1; - 3 次扫描卷积;
- 多进程带宽扩展测试,k = 1、2、4、8、… 个进程,最多到插槽 0 上的物理核心数。
为多线程对比添加 --mt。它在单个进程中针对多线程 MKL 和多线程 FFTW 运行 pmfft_mt,线程数从 1、2、4……直到 socket 0 上的物理核心数(在混合架构 Intel CPU 上包括 E 核),并且在启用计数器时以满线程数产生 DRAM 流量。FFTW 会为每个 (线程数, n) 组合规划最多 FFTW_TIMELIMIT_MT 秒(默认 300),因此 --mt 大约增加 1 – 3 小时;多核服务器处于时间较长的一端。它需要 build/bench_dft_mt,而 scripts/build.sh 会在 setup_third_party.sh 使用 OpenMP 构建 FFTW 后构建一次。要单独运行它:uv run scripts/scale_mt.py --quick(冒烟测试)或 uv run scripts/scale_mt.py --sizes 2^22,2^24。在笔记本电脑上,请保持机器接通电源并保持凉爽。持续的全核负载会触发功耗限制,使结果偏移 10 – 20%。
添加 --cache-transition 以对跨越 L2、LLC 和 2 – 8 × LLC 边界的 97 个平滑尺寸进行密集扫描(约 1 小时;增加一个相图图),并添加 --factorial 以对 NT 存储 × 批处理 × 融合转置 × 拆分策略进行完整的 2^4 因子消融,并在对数运行时间上使用交互模型(约 1 小时)。两者在启用时都使用 DRAM 计数器。
另外五个选项用于测试结果是否稳健且有用:
--fairness(约 3–4 小时):协议敏感性。它涵盖原地、冷缓存、全新数组、4 KB 或全大页、带和不带 wisdom 的 FFTW ESTIMATE / MEASURE / PATIENT、首次调用计时、x86-64-v3 构建以及匹配协议比较。--repro(约 2–3 小时):可重复性研究,其终点预先固定在 results/ENDPOINTS.md 中。它运行 10 个块,保留每个样本,记录时钟和温度,并报告块自助法区间。它运行两次:使用普通分配的调用方数组(主要研究,repro_plain.*)和使用大页上的调用方数组(repro.*)。--caller-pages(约 40 分钟):调用方数组的分页如何改变每个库(大页与普通分配,交错进行),以及暴露 L2 组相联冲突的跨步收集微基准(GB/s 与行数、段大小、页大小、跨步填充)。--overhead(约 45 分钟):规划与内存开销。它报告 1 到 10,000 次变换的总时间、盈亏平衡重用次数,以及规划后保留的内存、规划峰值和执行暂存内存。--contention(~2 h): controlled bandwidth contention, in two parts:- (A) 1 到所有核心的独立进程,带有 DRAM 流量和时钟;
- (B) 一个 FFT 旁边有一个 STREAM-triad 负载,其带宽被测量。在具有多个 L3 域的 CPU 上,B 既运行负载共享 FFT 核心的 L3 的情况,也运行负载仅共享 DRAM 的情况。
--ceiling(约 1 小时)对两遍计划进行计时,分别去除其算术运算、去除其内存流量,以及作为两个普通流式遍。这显示了时间中有多少是数据移动、多少是算术运算,以及两者重叠了多少。它还映射了大素数的高斯重采样 DFT 的误差预算。
--all 开启所有可选研究(--ablations --mt --factorial --cache-transition --fairness --repro --overhead --contention --caller-pages --ceiling)。
添加 --ablations 以重新检查关键思想是否适用于你的硬件:非临时存储、批处理、四步拆分形状和显式转置。
DRAM 流量计数器(推荐)。如果允许系统级 perf 计数器,运行还会测量每次变换的 DRAM 字节数。在运行前启用它们(无需重启;该设置在重启时恢复):
sudo sysctl kernel.perf_event_paranoid=0
计数器支持:
- Intel:内存控制器(IMC)计数器。
- AMD Zen 2/3:数据织物 DRAM 通道计数器,读 + 写合并。如果
/sys/bus/event_source/devices没有amd_df,请先加载 uncore 驱动:sudo modprobe amd_uncore。 - WSL2 和大多数虚拟机:不可用,因此跳过此步骤。
传递 --no-counters 以跳过它。
获得干净数字的提示
- 关闭其他应用程序。
- 尽可能使用
performance调速器:sudo cpupower frequency-set -g performance。 - 将透明大页设置为
madvise或always。 - 选择核心。 单核运行会固定到最快的物理核心:在混合架构的 Intel 处理器上
即为 P-core,并避开 CPU 0。扩展性测试为插槽 0 的每个物理核心使用一个逻辑 CPU,
因此在多插槽服务器上它会保持在同一个内存控制器上。要覆盖此设置:
BENCH_CPU=<cpu>和BENCH_CPUS=<a,b,c,...>。 - AMD CPU。 MKL 是 Intel 的库,在 AMD 上可能不会使用其最佳代码路径。
与 FFTW 的对比是厂商中立的,后者在此处针对你的 CPU 使用
-march=native构建。 - FFTW planning time. FFTW_MEASURE planning is capped at
FFTW_TIMELIMITseconds per size (default 60). On slower machines the large sizes can hit the cap, which yields a truncated, slow plan;summary.mdflags those sizes. For a fair comparison there:- 删除
results/machines/<label>/fftw_wisdom.dat; - 使用
FFTW_TIMELIMIT=600 scripts/run_machine.sh <label>重新运行。每个受影响的规模大约会增加 10 分钟的 规划时间。
- 删除
- 虚拟机和 WSL2。 CPU 固定针对的是虚拟 CPU,由宿主机进行调度,因此预计
会有更多噪声。请在标签中注明,例如
ryzen-3900x-wsl。
贡献结果
完整运行后,results/machines/<label>/ 包含:
- 原始 CSV 文件;
machine.txt,包含 lscpu、内核、编译器、调频器和内存大小(不含主机名或用户数据);summary.md;- 日志;
- 所使用的 FFTW wisdom。
results/machines/COMPARISON.md 将所有机器并排展示。将该目录与重新生成的 COMPARISON.md 一起提交,
并打开一个拉取请求:
uv run scripts/compare_machines.py git add results/machines/<label> results/machines/COMPARISON.md
复现完整研究
scripts/run_all.sh 在参考布局 results/*.csv 上重新运行所有消融实验、最终对比、Toeplitz 测试(T1–T3)以及
分析。大约需要 70 分钟。uv run scripts/analyze.py
重新生成 results/tables.md 和图表。
布局
csrc/common/common.h aligned / huge-page allocation, timers, AVX2 complex helpers, accurate twiddles
csrc/ref/ref.h __float128 reference DFT (error measurement)
csrc/dft/stockham.h batched mixed-radix Stockham engine, fused radix-2^f passes [I2, I9]
csrc/dft/rec4.h recursive batched four-step, fused transposes, NT stores [I1, I3, I4, I5, I11]
csrc/dft/rdit.h depth-first recursive DIT for mid sizes
csrc/dft/arbitrary.h Bluestein, Rader, Gaussian-resampling DFT [I7]
csrc/bench/impl_ours.c all our variants + pmfft (the measuring planner), pmfft_heur, pmfft_mt
csrc/bench/impl_ext.c FFTW / MKL wrappers; impl_ducc.cpp: ducc0 wrapper
csrc/bench/bench_dft.c driver -> CSV (time, GFlop/s = 5 n log2 n / t, error vs quad reference)
csrc/bench/bench_conv.c cyclic convolution: library vs 3-sweep transposed-spectrum fusion [I9]
csrc/bench/bench_2d.c 2-D: Nussbaumer polynomial transform vs row-column [I8]
csrc/tests/ codelet/engine tests, transpose & axis-permutation benchmarks [I1-toy, I14]
tests/test_toeplitz.py Toeplitz representation tests T1-T3
scripts/ setup_third_party.sh, build.sh, run_machine.sh, run_all.sh, run_bench.py,
scale_mp.py, scale_mt.py, mem_traffic.py, cache_transition.py (+ plot_cache_transition.py),
factorial.py, compare_machines.py, analyze.py, gen_codelets.py, cpus.py
results/ reference-machine data, REPORT.md, tables.md, figures; machines/<label>/ per machine
引用
如果你使用 SweepFFT 或其结果,请引用该论文 (doi:10.5281/zenodo.23322613):
@misc{cyranka2026sweepfft, author = {Cyranka, Jacek}, title = {{SweepFFT}: Pass-Minimal Algorithms for Out-of-Cache Fast {Fourier} Transforms}, year = {2026}, publisher = {Zenodo}, note = {Working paper, version 1}, doi = {10.5281/zenodo.23322613}, url = {https://doi.org/10.5281/zenodo.23322613} }
许可证
GPL-2.0-or-later(见 LICENSE)。
该基准测试链接以下库。scripts/setup_third_party.sh 会下载它们;它们
不随本仓库分发:
- FFTW(GPL-2.0-or-later);
- ducc0(GPL-2.0-or-later);
- Intel MKL(Intel Simplified Software License)。
来源:Hacker News · github.com