性能分析工具入门:nvprof与Nsight Systems的使用
做CUDA优化,最怕什么?
怕瞎调。调了半天,性能没涨,反而更慢了。
我刚开始做GPU加速的时候,就犯过这个错。拿到一个kernel,觉得这里该优化,那里该改,结果折腾一周,加速比从10倍掉到了8倍。后来我才明白——没有数据支撑的优化,都是耍流氓。
所以这一章,咱们聊聊性能分析工具。说白了,就是给你的kernel做一次「体检」,看看它到底慢在哪。
2.1 两个工具,各司其职
NVIDIA提供了两代性能分析工具:nvprof和Nsight Systems。
nvprof是经典款,命令行操作,轻量级。Nsight Systems是新一代,图形界面,功能更全。
我个人习惯:快速定位用nvprof,深入分析用Nsight Systems。
核心区别:
- nvprof:适合看单个kernel的详细指标,比如占用率、带宽、指令数
- Nsight Systems:适合看整个应用的时序图,CPU和GPU的活动一目了然
2.2 nvprof实战:三行命令看透kernel
先来个最简单的用法。假设你有一个程序叫my_cuda_app,想看看它跑得怎么样:
nvprof ./my_cuda_app
输出会列出每个kernel的耗时、调用次数、平均时长。嗯,这是入门级。
但真正有用的,是下面这几个参数:
# 看占用率
nvprof --metrics achieved_occupancy ./my_cuda_app
# 看带宽
nvprof --metrics gld_efficiency,gst_efficiency ./my_cuda_app
# 看计算与访存比例
nvprof --metrics inst_per_warp ./my_cuda_app
我在项目中遇到过一个问题:一个kernel跑得很慢,但占用率显示95%。当时我就纳闷了——占用率这么高,怎么还慢?
后来用gld_efficiency一看,全局内存加载效率只有12%。说白了,线程虽然都在跑,但都在等数据。这就是典型的访存密集型瓶颈。
小技巧:如果你不确定该看哪个指标,先看achieved_occupancy和gld_efficiency。这两个指标能帮你快速判断是计算瓶颈还是访存瓶颈。
2.3 识别性能瓶颈:计算密集型 vs 访存密集型
怎么区分?我一般看两个指标:
| 瓶颈类型 | 典型特征 | 占用率 | 带宽效率 |
|---|---|---|---|
| 计算密集型 | 算术指令多,访存少 | 通常较低 | 较高 |
| 访存密集型 | 大量全局内存访问 | 可能很高 | 很低 |
| 混合型 | 两者都有 | 中等 | 中等 |
你想想看,如果一个kernel的算术指令占比超过80%,那就是计算密集型。这时候优化方向是:减少冗余计算、使用更快的数学函数、提高指令级并行。
反过来,如果访存指令占比高,那就得从合并访问、共享内存、数据复用这些方向下手。
我曾经优化过一个矩阵乘法kernel。一开始看inst_per_warp,发现计算指令和访存指令比例是1:3。嗯,访存瓶颈。后来用共享内存做分块,比例变成了3:1,性能提升了4倍。
注意:不要只看一个指标就下结论。比如占用率高不一定好,也可能是线程都在空转。要结合多个指标综合判断。
2.4 Occupancy分析:到底多少线程才够?
Occupancy,中文叫「占用率」。它表示每个SM上活跃的warp数量与最大warp数量的比值。
很多人以为占用率越高越好。其实不一定。
我举个例子:一个kernel每个线程用128个寄存器,那一个SM最多只能同时跑8个warp(因为寄存器有限)。占用率可能只有25%。但如果你把寄存器用量降到32个,占用率就能到100%。
问题是——占用率100%就一定快吗?
不一定。如果这个kernel是计算密集型的,寄存器少了,反而会导致更多的本地内存溢出(spill),性能反而下降。
所以我的建议是:
- 访存密集型:尽量提高占用率,用更多的线程来隐藏访存延迟
- 计算密集型:适当降低占用率,给每个线程更多寄存器,减少spill
用nvprof看占用率很简单:
nvprof --metrics achieved_occupancy ./my_cuda_app
输出会显示实际达到的占用率。比如:
Achieved Occupancy: 0.875 (87.5% of theoretical)
87.5%,还不错。但如果低于50%,你就得想想是不是寄存器或共享内存用太多了。
2.5 Nsight Systems:时间线视角
nvprof能告诉你「每个kernel有多慢」,但Nsight Systems能告诉你「为什么慢」。
它最厉害的地方是时间线视图。你能看到CPU在干什么,GPU在干什么,数据传输在什么时候发生。
我遇到过这样一个场景:一个推理应用,GPU利用率只有40%。用nvprof看,每个kernel都很快。但用Nsight Systems一看,发现问题了——CPU和GPU之间频繁同步,每次同步都浪费了几十微秒。
这就是典型的「同步开销」瓶颈。优化方案是:用CUDA Stream把多个kernel流水线化,减少同步点。
使用建议:Nsight Systems的GUI界面很直观。你只需要:
- 打开Nsight Systems
- 选择你的可执行文件
- 点击「Start」开始分析
- 看时间线,找「空隙」和「等待」
2.6 知识体系:一张图看懂性能分析
下面这张SVG图,总结了本章的核心逻辑。你可以把它当作「性能分析路线图」:
2.7 避坑指南:我曾经踩过的三个坑
坑一:只看kernel时间,不看数据传输
我曾经优化一个kernel,从10ms优化到了2ms,开心得不行。结果一测总时间,还是100ms。为什么?因为数据传输占了98ms。后来我才意识到,数据传输才是真正的瓶颈。所以用Nsight Systems看时间线,先看数据传输占比。
坑二:盲目追求高占用率
有次我把一个kernel的占用率从30%调到了90%,结果性能反而下降了。原因是寄存器被压缩后,大量变量溢出到了本地内存,访存延迟暴增。所以记住:占用率不是越高越好,要结合具体场景。
坑三:忽略warp发散
nvprof有个指标叫branch_efficiency,我一开始没在意。后来发现一个kernel的branch_efficiency只有40%,意味着60%的线程在分支中处于闲置状态。优化分支后,性能提升了2倍。所以分支发散也是隐形杀手。
2.8 小结
这一章咱们聊了:
- nvprof和Nsight Systems怎么用
- 怎么区分计算密集型和访存密集型瓶颈
- Occupancy分析的正确姿势
- 三个实战中容易踩的坑
工具只是手段,关键是学会看数据、分析数据。下一章咱们会深入一个具体的优化案例,把这些工具用起来。
想要更多资料,联系可可