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可以用来切换。

我画了一张图,帮你理解这四者的关系:

CUDA性能指标核心关系图 占用率 活跃warp比例 内存带宽利用率 实际/理论带宽 计算吞吐量 TFLOPS 延迟 400-800周期 提供warp切换能力 高利用率减少等待 高吞吐降低计算延迟 四个指标相互影响,找到平衡点才是关键

你看,这四个指标是环环相扣的。占用率高了,warp切换的余地就大,延迟就能被隐藏。内存带宽利用率高了,数据喂得快,计算单元就不空等。计算吞吐量高了,每个周期干的事多,整体延迟自然就下来了。

4.5 实战:怎么用这些指标定位瓶颈?

我一般按这个顺序排查:

  1. 先看内存带宽利用率:如果低于50%,大概率是访存模式有问题
  2. 再看计算吞吐量:如果利用率高但吞吐低,可能是计算密度不够
  3. 然后看占用率:如果低于50%,检查寄存器、shared memory用量
  4. 最后看延迟:用ncu --section MemoryWorkloadAnalysis看具体延迟分布

小技巧:用ncu --set full可以一次性拿到所有指标。但数据量很大,我建议你先用--set basic快速扫一遍,再针对性地加详细指标。

嗯,最后说一句。这些指标不是孤立的,你得把它们放在一起看。比如占用率低但带宽利用率高,说明你的kernel可能计算量很大,寄存器用得多,但访存效率不错。这时候就别纠结占用率了,该考虑怎么减少寄存器使用。

好了,这一节就到这儿。记住:指标是工具,不是教条。多跑几次profiler,多看看数据,慢慢你就会有感觉了。

一句话总结:占用率提供隐藏延迟的资本,带宽利用率决定数据供给速度,计算吞吐量反映SM的真实算力,延迟是最终要解决的问题。四者平衡,才是好kernel。

想要更多资料,联系可可