性能分析工具入门: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界面很直观。你只需要:

  1. 打开Nsight Systems
  2. 选择你的可执行文件
  3. 点击「Start」开始分析
  4. 看时间线,找「空隙」和「等待」

2.6 知识体系:一张图看懂性能分析

下面这张SVG图,总结了本章的核心逻辑。你可以把它当作「性能分析路线图」:

CUDA性能分析路线图 选择分析工具 nvprof(命令行) Nsight Systems(GUI) 关键指标:占用率、带宽效率、指令比例 判断瓶颈类型 计算密集型 访存密集型 减少冗余计算 合并访问+共享内存

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分析的正确姿势
  • 三个实战中容易踩的坑

工具只是手段,关键是学会看数据、分析数据。下一章咱们会深入一个具体的优化案例,把这些工具用起来。

想要更多资料,联系可可