2. 性能分析工具入门
做GPU优化,最怕什么?
怕你埋头改了一整天代码,结果性能没提升,反而更慢了。
我刚开始接触CUDA时,就犯过这个错。那时候我凭感觉调优,觉得循环展开肯定快,结果一跑,比原来还慢30%。后来老工程师跟我说了一句话,我记到现在:「没有测量,就没有优化」。
所以这一章,咱们就来聊聊性能分析工具。说白了,就是给你的GPU程序做「体检」。
为什么需要性能分析工具?
你想想看,一个CUDA程序跑得慢,原因可能有一百种:
- 是显存带宽不够?
- 还是计算单元没喂饱?
- 或者是核函数启动开销太大?
- 甚至可能是CPU和GPU之间的数据传输卡住了?
没有工具,你只能靠猜。有了工具,你就能看到每一毫秒GPU在干什么。
核心原则:先测量,后优化。不要相信直觉,要相信数据。
工具全家福:三个工具各司其职
NVIDIA给了我们三把利器。我习惯把它们分成三个层次:
| 工具 | 定位 | 我什么时候用它 |
|---|---|---|
| Nsight Systems | 系统级时间线分析 | 先看整体,找瓶颈在哪 |
| Nsight Compute | Kernel级深度分析 | 找到瓶颈后,钻进去看细节 |
| nvprof/ncu | 命令行快速 profiling | 自动化脚本、CI集成、远程服务器 |
嗯,这里要注意:顺序很重要。我见过有人一上来就用Nsight Compute分析一个Kernel,结果发现那个Kernel只占总时间的5%——这不是白费功夫吗?
Nsight Systems:先看全局
Nsight Systems给我的感觉,就像给程序拍了一张「心电图」。它能展示整个程序运行的时间线,包括:
- CPU端的函数调用
- GPU端的Kernel执行
- 内存拷贝操作(cudaMemcpy)
- CUDA API调用
我个人习惯,拿到一个程序,第一步就是跑Nsight Systems。为什么?因为我要先回答三个问题:
- 瓶颈在CPU还是GPU?——看时间线上谁占大头
- 有没有不必要的同步?——看CPU是不是在等GPU
- 数据传输是不是太多了?——看cudaMemcpy的占比
小技巧:在Nsight Systems里,你可以按住Ctrl+鼠标滚轮来缩放时间线。我经常放大到纳秒级别,看Kernel之间的间隙——那些间隙往往藏着优化空间。
Nsight Compute:深入Kernel内部
当你用Nsight Systems找到了「嫌疑Kernel」,就该Nsight Compute出场了。
这个工具能告诉你一个Kernel内部发生了什么:
- 计算吞吐利用率(Compute Utilization)
- 显存带宽利用率(Memory Bandwidth Utilization)
- Warp占用率(Occupancy)
- 指令混合(Instruction Mix)
- L1/L2缓存命中率
我记得有一次,我优化一个矩阵乘法的Kernel。Nsight Compute告诉我,计算利用率只有30%,但显存带宽利用率已经95%了。这说明什么?说明这个Kernel是带宽受限的,你再怎么优化计算也没用。得从数据复用、访存模式上想办法。
避坑指南:我曾经被Occupancy这个指标骗过。当时我拼命提高Occupancy,结果性能反而下降了。后来才发现,Occupancy高不代表性能好——如果每个线程做的事情太少,高Occupancy只会带来更多的线程调度开销。记住:Occupancy是手段,不是目标。
nvprof/ncu:命令行下的利器
有时候你没有图形界面,比如在远程服务器上。这时候nvprof(老版本)和ncu(新版本)就是你的好帮手。
基本用法很简单:
# 老版本(CUDA 10.x及之前)
nvprof ./my_cuda_program
# 新版本(CUDA 11.x及之后)
ncu ./my_cuda_program
但光这样跑,信息太多了。我一般会加一些参数:
# 只看Kernel的概要
ncu --set full ./my_cuda_program
# 导出到文件,方便对比
ncu -o profile_result ./my_cuda_program
# 只看某个Kernel
ncu --kernel-name "my_kernel" ./my_cuda_program
嗯,这里有个实用场景。我在做CI(持续集成)的时候,会把ncu集成到自动化脚本里。每次提交代码,自动跑一遍profiling,如果某个Kernel的性能下降了超过5%,就报警。这样能及时发现性能回退。
实战:一个完整的分析流程
说了这么多,咱们走一遍完整的流程。假设你有一个程序,感觉跑得慢:
- 第一步:Nsight Systems扫全局
- 发现80%的时间花在一个叫matmul的Kernel上
- 还有15%的时间在cudaMemcpy上
- 第二步:Nsight Compute分析matmul
- 发现显存带宽利用率只有40%
- L1缓存命中率很低
- 说明访存模式有问题
- 第三步:优化访存
- 改成共享内存分块(tiling)
- 确保合并访问(coalesced access)
- 第四步:用ncu验证
- 跑ncu对比优化前后的指标
- 确认显存带宽利用率提升到了75%
- 整体性能提升了1.8倍
你看,这个流程里,每个工具都在它该在的位置上。
知识体系总览
下面这张图,是我自己总结的GPU性能分析知识体系。你可以把它当作一个检查清单:
一些实用建议
最后,分享几个我踩过的坑:
- 不要在生产环境直接profiling。Profiling本身有开销,会影响性能数据。我一般会在专门的测试环境跑。
- 多次运行取平均值。GPU性能有波动,单次运行的数据不可靠。我习惯跑5次,取中位数。
- 对比优化前后,保持环境一致。包括GPU频率、温度、驱动版本。否则你分不清是优化有效,还是环境变了。
- 学会看「瓶颈」而不是「指标」。Occupancy 100%不代表性能好,带宽利用率100%才说明你喂饱了显存。
我的工作流:每次优化前,我会用ncu导出一份基线数据。优化后,再导出一份。然后用ncu --import对比两份数据,一眼就能看出哪些指标变了。这个习惯帮我避免了很多「自以为优化了,实际没变化」的尴尬。