为何CuFFT吞吐量随变换规模增大线性增长?自研FFT如何优化?
自研1D FFT与CuFFT性能差异原因及优化策略
性能差异的核心原因
1. 并行度扩展能力不足
自研FFT在规模超过2^19后,线程网格/块配置未随输入规模同步扩展,导致GPU Streaming Multiprocessor(SM)资源被完全占满后无法进一步利用更多硬件单元,吞吐量陷入瓶颈。而CuFFT会根据输入规模动态调整并行策略:
- 大尺寸FFT会被拆分为多个独立的子FFT任务,分配给更多SM并行处理
- 动态调整任务粒度,保证所有SM始终处于高负载状态
2. 内存访问效率瓶颈
自研版本未针对大规模数据优化内存访问模式:
- 全局内存未实现合并访问,导致内存事务数量激增,带宽利用率低
- 未充分利用共享内存做数据复用,频繁的全局内存读写成为性能瓶颈
CuFFT则针对不同规模做了精细化内存优化,比如分块加载数据到共享内存完成蝶形运算,最大化内存带宽利用率。
3. 计算与调度优化差距
- CuFFT充分利用CUDA硬件特性:如Warp Shuffle指令减少共享内存依赖、Tensor Core加速(支持时)、向量运算向量化等,大幅提升计算效率
- 自研版本的蝶形运算映射、负载均衡策略不够完善,小规模时能占满硬件资源,但规模增大后无法高效调度更多计算单元
- 小尺寸FFT时,CuFFT的启动开销占比高,导致吞吐量低;随着规模增大,启动开销占比降低,吞吐量呈现线性增长,而自研版本的启动开销占比随规模增大变化不明显,早早就达到硬件利用率上限。
自研FFT优化策略(含网格大小策略)
一、网格与线程块配置优化
- 动态调整网格规模:根据输入尺寸
N计算最优线程块数量,确保线程块数为GPU SM数量的2-4倍(保证SM的高效利用)。例如,当N=2^25时,可将FFT拆分为多个2^16的子FFT,每个线程块处理一个子FFT,通过增加线程块数量让更多SM参与计算。 - 线程块大小适配:选择符合Warp倍数的线程块大小(如256、512),并根据FFT阶段调整:位反转阶段可使用较大线程块提升内存访问效率,蝶形运算阶段根据子FFT尺寸选择适配的线程块大小,最大化Warp内协作。
- 多批次并行打包:若单批次FFT无法占满GPU,将多个独立FFT任务打包为一个批次,通过扩展网格维度实现多批次并行,提升整体吞吐量。
二、内存访问优化
- 全局内存合并访问:优化位反转后的内存布局,确保线程访问的内存地址连续,符合CUDA合并访问规则,减少内存事务开销。
- 共享内存复用:在蝶形运算阶段,将数据加载到共享内存中完成多轮运算,减少全局内存读写次数。例如,每个线程块加载一组子FFT数据到共享内存,在共享内存内完成所有蝶形运算后再写回全局内存。
- 寄存器与Warp Shuffle替代:针对小尺寸蝶形运算,使用寄存器存储中间结果,通过
__shfl_down_sync等Warp Shuffle指令替代共享内存访问,降低延迟。
三、计算逻辑优化
- 向量化蝶形运算:使用CUDA向量类型(
float2/double2),让每个线程同时处理多个复数元素,提升计算吞吐量。 - 预计算旋转因子:将FFT所需的旋转因子预计算并存储在常量内存或共享内存中,避免重复计算,减少运算开销。
- 硬件特性利用:若GPU支持Tensor Core,可尝试将部分FFT计算映射到Tensor Core;在精度允许的情况下,使用FP16/TF32低精度格式提升计算速度。
四、任务调度优化
- 子FFT负载均衡:将大尺寸FFT拆分为多个尺寸相同的子FFT,分配给不同线程块处理,确保每个SM的负载均衡,避免出现部分SM空闲的情况。
- 计算与拷贝重叠:若涉及主机-设备数据传输,使用CUDA流实现计算与内存拷贝的重叠,隐藏数据传输延迟。
测试性能数据对比
| 变换规模 | 自研FFT吞吐量 | 自研FFT耗时 | CuFFT吞吐量 | CuFFT耗时 |
|---|---|---|---|---|
| 2^16 | 1.3 Gf | 3.8 msec | 0.19 Gf | 27 msec |
| 2^17 | 1.6 Gf | 6.7 msec | 0.5 Gf | 23 msec |
| 2^18 | 1.8 Gf | 13 msec | 1.1 Gf | 22 msec |
| 2^19 | 1.8 Gf | 27 msec | 2.1 Gf | 23 msec |
| 2^20 | 1.8 Gf | 56 msec | 4.5 Gf | 23 msec |
| 2^21 | 1.8 Gf | 121 msec | 9.6 Gf | 22 msec |
| 2^23 | 1.8 Gf | 523 msec | 31 Gf | 30 msec |
| 2^25 | 1.8 Gf | 2298 msec | 106 Gf | 39 msec |
内容的提问来源于stack exchange,提问作者user2988096




