必须一次规划、反复执行以实现低延迟FFT;用fftw_malloc对齐内存;实数输入优先用r2c/c2r;多线程需隔离缓冲区,共享plan;避免实时线程中创建/销毁plan。

用 fftw3 做低延迟 FFT,必须绕开 plan 创建开销
实时音频或传感器流处理中,fftw_plan_dft_1d 这类函数不能在每帧数据来时都调用——它内部会测量多种算法变体的执行时间,耗时可达毫秒级。真正的低延迟做法是「一次规划、反复执行」。
- 初始化阶段调用
fftw_plan_dft_1d(或fftw_plan_dft_c2c_1d),传入固定长度、固定内存布局的输入/输出指针,并加FFTW_MEASURE或FFTW_PATIENT获取最优 plan - 运行时只调用
fftw_execute,它通常在微秒级完成(比如 1024 点复数 FFT 在现代 CPU 上约 5–15 μs) - 绝对不要在实时线程里调用
fftw_destroy_plan或重新fftw_plan_*—— 这会触发锁和内存重分配,极易导致卡顿 - 若需支持动态长度,提前为常见尺寸(如 256/512/1024/2048)各建一个 plan 并缓存,用查表代替实时生成
std::vector 和 fftw_complex 内存对齐不匹配会直接崩溃
FFTW 默认要求输入/输出数组地址按 16 字节(SSE)或 32 字节(AVX)对齐,而 std::vector<:complex>></:complex> 的分配器不保证这点。未对齐访问在某些 CPU 上触发 bus error,或静默降速数倍。
- 改用
fftw_malloc分配输入/输出缓冲区:fftw_complex* in = reinterpret_cast<fftw_complex>(fftw_malloc(sizeof(fftw_complex) * N))</fftw_complex> - 对应必须用
fftw_free释放,不能混用delete或free - 如果坚持用
std::vector,得配合自定义分配器(如aligned_allocator),但增加维护成本,不推荐在硬实时路径使用 - 检查是否对齐:打印
reinterpret_cast<uintptr_t>(in) % 32</uintptr_t>,非零即未对齐
实数输入 FFT 用 fftw_plan_dft_r2c_1d 节省一半计算量
传感器、麦克风等原始数据几乎全是实数序列,直接转成复数再做 c2c 是浪费:不仅多占一倍内存,还多算冗余共轭部分。
- 用
fftw_plan_dft_r2c_1d,输入是double*,输出是fftw_complex*,但只写入前N/2+1个复数(因实数 FFT 具有共轭对称性) - 注意输出长度不是
N,而是N/2+1—— 若误按N解析,后半段读到的是未定义内存 - 逆变换用
fftw_plan_dft_c2r_1d,输出需手动除以N才得原始幅度(FFTW 不自动归一化) - 该路径比等长
c2c快约 1.7×,且 cache 友好性更好
线程安全与 plan 复用边界必须明确
fftw_execute 本身是线程安全的,但前提是每个线程操作自己的 plan 和缓冲区。共享 plan 没问题,共享输入/输出内存则必然出错。
立即学习“C++免费学习笔记(深入)”;
- 多个线程可并发调用同一
fftw_plan,只要各自传入独立的in/out指针 - 不要让两个线程同时往同一块
in写数据、又同时用同一个 plan 执行——结果取决于写入顺序,不可预测 - 若用 OpenMP 或 std::thread 做并行批处理,确保每个 worker 持有自己的一组缓冲区 + 共享 plan
- FFTW3 默认不启用线程支持;如需内部多线程加速(如大点数 FFT),需链接
fftw3_threads并调用fftw_init_threads(),但这反而可能增加调度抖动,实时场景通常关掉更稳
实际最难的部分不是 FFT 本身,而是把 plan 生命周期、内存生命周期、数据就绪时机三者在时间线上严丝合缝地咬合——差一个帧,就丢一次中断,或者多一次等待。











