1. CUDA性能模型概述:理解延迟与吞吐量、计算与访存比、Roofline模型入门

各位同学,欢迎来到CUDA性能剖析的第一课。

说实话,我见过太多人一上来就写核函数,跑一遍发现慢了,就开始瞎调——改改block大小,换换循环顺序,碰运气。这样搞,效率极低。

我个人习惯是,动手优化之前,先想清楚一个问题:我的程序到底被什么卡住了?

是计算太多?还是数据搬得太慢?还是两者都有?

搞明白这个,你才能对症下药。今天我们就来聊聊CUDA性能模型里最核心的三个概念:延迟与吞吐量、计算与访存比,以及Roofline模型。

核心观点:GPU优化的本质,就是让计算单元别闲着,让数据搬运别堵车。你所有的调优手段,最终都是为了平衡这两件事。

1.1 延迟 vs 吞吐量:两个不同的“快”

先问个问题:你觉得一个操作“快”,是什么意思?

举个例子。你去食堂打饭,从排队到拿到饭花了10秒钟,这叫延迟低。但食堂一次只能给一个人打饭,高峰期后面排了100个人,每个人都要等10秒,总时间就是1000秒。这叫吞吐量低。

GPU的设计思路恰恰相反。它不在乎你一个人等多久,它在乎的是单位时间内能处理多少个任务。

  • 延迟(Latency):一个操作从发出到完成的时间。比如一次全局内存读取,可能需要几百个时钟周期。
  • 吞吐量(Throughput):单位时间内完成的操作数量。比如GPU每秒能执行多少万亿次浮点运算(TFLOPS)。

我刚开始做CUDA时,总想把延迟压到最低。后来发现,在GPU上,隐藏延迟比降低延迟更重要。

怎么隐藏?靠并行。当一个线程在等数据时,GPU立刻切换到另一个线程去干活。只要你有足够多的线程,延迟就被“藏”起来了。

我的经验:判断一个kernel是否被延迟隐藏了,可以看occupancy(占用率)。如果occupancy太低,说明线程数不够,延迟藏不住,性能就会差。我一般会先保证occupancy在50%以上,再考虑其他优化。

1.2 计算与访存比:你的程序是“算得多”还是“搬得慢”?

每个CUDA程序,本质上就干两件事:计算和访存。

计算就是做加减乘除,访存就是从显存里读数据、写数据。

那问题来了:你的程序到底是计算密集型的,还是访存密集型的?

区分这个很简单,看一个指标——计算与访存比(Arithmetic Intensity, AI)。

公式长这样:

AI = 总的浮点运算次数(FLOPs) / 总的显存访问字节数(Bytes)

单位是FLOPs/Byte。这个值越大,说明每个字节的数据被反复用了很多次,程序偏向计算密集型。这个值越小,说明数据读进来就用一次,程序偏向访存密集型。

举个例子:

  • 矩阵乘法:一个MxN的矩阵乘以NxK的矩阵,需要2*M*N*K次浮点运算,但只读入M*N + N*K个元素。AI值很高,是典型的计算密集型。
  • 向量加法:读两个向量,写一个向量,中间只做一次加法。AI值很低,是典型的访存密集型。

我遇到过不少同学,拿着一个访存密集型的程序,拼命优化计算指令,结果性能纹丝不动。为什么?因为瓶颈根本不在计算,在显存带宽。

避坑指南:我曾经优化过一个图像处理kernel,把循环展开、用intrinsics指令,折腾了两天,性能只提升了3%。后来一算AI,发现只有0.5 FLOPs/Byte。说白了,程序95%的时间都在等数据。我后来改用共享内存合并访存,性能直接翻倍。所以,先算AI,再动手。

1.3 Roofline模型入门:一张图看懂瓶颈

好了,现在我们知道两个关键信息:

  1. GPU的计算能力(峰值FLOPs)
  2. GPU的访存能力(峰值带宽)

再加上我们算出来的AI值,就能画出一张非常有用的图——Roofline模型。

这张图长什么样?我画给你看。

Roofline 模型示意图 计算与访存比 (FLOPs/Byte) → 0.1 1 10 100 性能 (GFLOPs) → 1 10 100 1000 带宽上限 (GB/s) 计算上限 (TFLOPs) 拐点 访存受限区 计算受限区 A (访存瓶颈) B (计算瓶颈) C (理想)

这张图怎么看?我解释一下。

  • 横轴:计算与访存比(AI),从左到右越来越大。
  • 纵轴:实际达到的性能(GFLOPs),从下到上越来越高。
  • 两条线:一条是访存带宽上限(斜线),一条是计算上限(水平线)。这两条线组成了一个“屋顶”。

你的程序,根据它的AI值,会落在屋顶下面的某个位置。

  • 点A:AI很低,落在斜线区域。说明性能被访存带宽卡住了。你优化计算没用,得优化数据访问模式。
  • 点B:AI很高,落在水平线区域。说明性能被计算能力卡住了。你优化访存没用,得优化计算指令。
  • 点C:刚好在屋顶上。说明你的程序已经接近硬件极限了,很难再优化了。

说白了,Roofline模型就是帮你回答一个问题:我的程序现在离天花板还有多远?瓶颈在哪边?

我的习惯:每次写完一个kernel,我都会用NVIDIA Nsight Compute跑一下,看看AI值和Roofline图。如果发现程序在访存受限区,我会优先考虑:合并访问、使用共享内存、减少全局内存访问次数。如果在计算受限区,我会考虑:使用更快的数学函数、减少分支、提高指令级并行。

1.4 三个概念的关系:一张表说清楚

我把今天讲的三个概念整理成了一张表,方便你对照。

概念 核心问题 关键指标 优化方向
延迟 vs 吞吐量 程序是等得久,还是干得慢? Occupancy、延迟隐藏 增加并行度,隐藏延迟
计算与访存比 瓶颈在计算还是访存? Arithmetic Intensity (FLOPs/Byte) 根据AI值选择优化策略
Roofline模型 离硬件极限还有多远? 实际性能 vs 理论峰值 定位瓶颈,针对性优化

这三个概念是层层递进的。你先理解延迟和吞吐量,知道GPU是怎么工作的。然后学会算AI,判断程序类型。最后用Roofline模型,把瓶颈可视化出来。

嗯,到这里,CUDA性能模型的基础框架就搭好了。后面几节课,我们会深入每个环节,讲具体的分析工具和优化技巧。

记住一句话:没有测量,就没有优化。先搞清楚瓶颈在哪,再动手。

课后小作业:找一个你写过的CUDA程序,算一下它的Arithmetic Intensity。然后想想,它落在Roofline模型的哪个区域?你打算怎么优化它?

可可精品资料库