4. 性能指标解读:占用率、内存带宽利用率、计算吞吐量、延迟
各位同学,今天我们来聊聊CUDA性能分析里最核心的几个指标。说实话,我见过太多人一上来就盯着占用率不放,觉得这玩意儿越高越好。其实不然。咱们得把这四个指标串起来看,才能找到真正的瓶颈。
4.1 占用率(Occupancy)—— 别被它骗了
占用率,说白了就是SM上活跃的warp数量占理论最大值的比例。比如你的GPU一个SM能跑64个warp,现在跑了48个,那占用率就是75%。
我刚开始做优化那会儿,总觉得占用率不到90%就是浪费。后来被一个老前辈点醒了:占用率只是手段,不是目标。你想想看,如果你的kernel是内存密集型的,那提高占用率反而可能让每个线程分到的带宽更少。
核心观点:占用率够用就行,别盲目追求100%。关键是看它能不能帮你隐藏延迟。
怎么算占用率?CUDA提供了现成的工具:
// 用CUDA Occupancy API计算理论占用率
int minGridSize, blockSize;
cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize,
myKernel, 0, 0);
// blockSize就是能最大化占用率的block大小
嗯,这里要注意:寄存器压力是占用率的头号杀手。我在项目中遇到过,一个kernel每个线程用了64个寄存器,结果占用率直接掉到33%。后来我硬是把一些中间变量换成shared memory,寄存器降到32个,占用率瞬间回到66%。
4.2 内存带宽利用率 —— 你的显存跑满了吗?
这个指标我特别看重。说白了,就是实际带宽除以理论带宽。比如RTX 3090的理论带宽是936 GB/s,你跑出来只有300 GB/s,那利用率就是32%。
为什么会这么低?我总结了几种常见情况:
- 非合并访问:线程访问的地址不连续,带宽利用率直接腰斩
- bank conflict:shared memory的bank冲突,虽然不影响全局带宽,但会拖慢计算
- 小数据量传输:每次只传几个字节,PCIe开销占比太大
我的经验:用ncu --metrics dram__bytes_read.sum,dram__bytes_write.sum可以快速看到实际读写量。如果实际带宽不到理论值的60%,你该考虑优化访存模式了。
举个例子,我曾经优化过一个矩阵转置的kernel。原始版本是行读取、列写入,带宽利用率只有25%。改成使用shared memory做分块转置后,利用率飙到了75%。
4.3 计算吞吐量 —— 你的SM在忙什么?
计算吞吐量,就是SM每秒能执行多少浮点运算。单位是TFLOPS。比如A100的FP32理论吞吐是19.5 TFLOPS。
但实际跑出来往往差很多。我见过一个kernel,理论计算量很大,但实际吞吐只有理论值的10%。为什么?因为指令级并行度不够。
CUDA编译器会尽量把独立的指令排在一起,让流水线不空转。但如果你的代码里到处都是数据依赖,比如:
float a = b + c; // 指令1
float d = a * e; // 指令2,依赖指令1的结果
float f = d + g; // 指令3,依赖指令2的结果
这种串行依赖,流水线只能等。我建议你多用--ptxas-options=-v看看编译器报告,里面会告诉你用了多少寄存器、有多少bank conflict。
避坑指南:我曾经在一个项目里,把所有的循环都展开了,以为能提高吞吐。结果寄存器爆了,占用率掉到谷底,性能反而下降了30%。展开要适度,别走极端。
4.4 延迟 —— 隐藏它,而不是消除它
延迟,就是一条指令从发出到完成的时间。全局内存访问延迟大概400-800个周期,而计算指令只要几个周期。
你没法消除延迟,但可以隐藏它。怎么隐藏?靠的就是warp切换。当一个warp在等数据时,SM会立刻切换到另一个就绪的warp。这就是为什么占用率重要——它决定了你有多少warp可以用来切换。
我画了一张图,帮你理解这四者的关系:
你看,这四个指标是环环相扣的。占用率高了,warp切换的余地就大,延迟就能被隐藏。内存带宽利用率高了,数据喂得快,计算单元就不空等。计算吞吐量高了,每个周期干的事多,整体延迟自然就下来了。
4.5 实战:怎么用这些指标定位瓶颈?
我一般按这个顺序排查:
- 先看内存带宽利用率:如果低于50%,大概率是访存模式有问题
- 再看计算吞吐量:如果利用率高但吞吐低,可能是计算密度不够
- 然后看占用率:如果低于50%,检查寄存器、shared memory用量
- 最后看延迟:用
ncu --section MemoryWorkloadAnalysis看具体延迟分布
小技巧:用ncu --set full可以一次性拿到所有指标。但数据量很大,我建议你先用--set basic快速扫一遍,再针对性地加详细指标。
嗯,最后说一句。这些指标不是孤立的,你得把它们放在一起看。比如占用率低但带宽利用率高,说明你的kernel可能计算量很大,寄存器用得多,但访存效率不错。这时候就别纠结占用率了,该考虑怎么减少寄存器使用。
好了,这一节就到这儿。记住:指标是工具,不是教条。多跑几次profiler,多看看数据,慢慢你就会有感觉了。
一句话总结:占用率提供隐藏延迟的资本,带宽利用率决定数据供给速度,计算吞吐量反映SM的真实算力,延迟是最终要解决的问题。四者平衡,才是好kernel。
想要更多资料,联系可可