计算机体系结构基础:冯·诺依曼架构、存储层次与并行计算

各位同学,欢迎来到第二章。说实话,每次讲这一章我都挺感慨的。为什么?因为很多人在GPU这条路上走不远,根子就出在计算机体系结构的基本功不扎实。你想想看,GPU再复杂,它也是建立在冯·诺依曼这个老框架上的。今天我们就来把这些基础掰开揉碎了讲清楚。

2.1 冯·诺依曼架构:一切计算的起点

1945年,冯·诺依曼提出了一个至今仍在统治计算机世界的架构。说白了,就是五个部件:运算器、控制器、存储器、输入设备、输出设备。核心思想就两条:存储程序指令顺序执行

我在项目中遇到过一件事。有次调试一个GPU的shader core,发现某个指令序列总是跑不对。查了三天,最后发现是程序计数器(PC)在分支预测失败时没正确更新。嗯,这就是冯·诺依曼架构里最基础的东西——指令流必须按地址顺序取,但现代处理器为了性能,早就开始“作弊”了。

核心要点:冯·诺依曼架构的瓶颈就是“存储墙”——CPU和内存之间的速度差距越来越大。GPU本质上是一个大规模并行化的冯·诺依曼机器,但它通过多线程隐藏延迟,绕开了这个瓶颈。

2.2 存储层次:为什么Cache这么重要?

你想想看,一个现代GPU的核心频率是2GHz,也就是0.5纳秒一个周期。但DRAM的访问延迟是多少?50-100纳秒。差了100倍!如果没有Cache,你的ALU大部分时间都在干等数据。

2.2.1 Cache的基本原理

Cache利用了程序的时间局部性空间局部性。时间局部性:你刚用过的数据,很可能马上再用。空间局部性:你访问了一个地址,旁边的地址很可能也会被访问。

我记得在参与某款移动GPU设计时,我们花了整整三个月调L2 Cache的替换策略。LRU(最近最少使用)听起来很完美,但实际跑起来,某些游戏场景下性能反而下降。为什么?因为LRU对流式访问模式不友好。最后我们改用了RRIP(Re-Reference Interval Prediction),效果好了不少。

避坑指南:我曾经在调试一个AI推理芯片时,发现矩阵乘法总是cache miss严重。后来一查,是数据排布问题——按行存储的数据,却按列去访问。改成平铺(tiling)后,命中率从30%飙升到95%。记住:数据排布决定Cache效率

2.2.2 DRAM:内存的物理现实

DRAM不是你想怎么访问就怎么访问的。它有行缓冲(row buffer),有bank,有rank。访问同一行很快,换行就很慢。这就是为什么GPU里要搞合并访问(coalesced access)——让同一个warp里的线程访问连续的地址,这样DRAM就能一次把整行数据读出来。

存储层级 典型延迟 容量 带宽
寄存器 ~0.5 ns ~256 KB 极高
L1 Cache ~1-2 ns ~128 KB ~10 TB/s
L2 Cache ~5-10 ns ~几MB ~1 TB/s
HBM2e ~100 ns ~16 GB ~1.2 TB/s

看到没?从寄存器到HBM,延迟差了200倍。这就是为什么GPU架构师每天都在跟存储层次较劲。

2.3 并行计算概念:ILP、TLP、DLP

并行计算不是只有GPU才有的概念。实际上,现代CPU里也充满了并行。我们分三种来看。

2.3.1 指令级并行(ILP)

ILP就是在单个线程里,让多条指令同时执行。靠什么?流水线超标量乱序执行。比如一个加法指令和一个乘法指令,如果它们没有数据依赖,就可以同时发射。

我刚开始做CPU设计时,总觉得ILP是万能的。直到有一次,我们给一个老架构加宽发射宽度,从4路加到8路,结果性能只提升了5%。为什么?因为指令级并行度是有上限的——程序里真正能并行执行的指令就那么几条。这就是所谓的“ILP墙”。

注意:GPU不依赖ILP。GPU的ALU很“傻”,它不会乱序执行,也不会做复杂的寄存器重命名。GPU靠的是TLP——用大量的线程来隐藏延迟。

2.3.2 线程级并行(TLP)

TLP就是同时运行多个线程。CPU的TLP很有限——一个核心通常只能跑1-2个线程(超线程)。但GPU呢?一个SM(Streaming Multiprocessor)可以同时跑上千个线程。

为什么会这样?因为GPU的线程切换代价极低。CPU切换线程要保存几十个寄存器、刷新TLB、更新页表,开销几百个周期。GPU呢?每个线程的上下文就是一组寄存器,切换时只需要换一组寄存器指针,零开销

我记得在设计一个GPU的warp scheduler时,我们纠结过一个问题:到底该用round-robin还是greedy-then-oldest?前者公平但延迟高,后者吞吐高但可能饿死某些线程。最后我们用了混合策略——平时用greedy,遇到长延迟操作时切到round-robin。

2.3.3 数据级并行(DLP)

DLP就是一条指令操作多个数据。这就是SIMD(单指令多数据)和SIMT(单指令多线程)的核心理念。

GPU的SIMT和CPU的SIMD有什么区别?CPU的SIMD(比如AVX-512)要求所有数据在同一个寄存器里,程序员要显式写向量代码。GPU的SIMT是硬件帮你做的——你写一个标量程序,硬件把它分发到32个线程(一个warp)上执行。如果这32个线程走了不同的分支,那就惨了——分支发散会导致部分线程被屏蔽,性能直接打折扣。

实战经验:我在优化一个图像处理kernel时,发现性能只有理论峰值的30%。一分析,发现是分支发散导致的——每个像素根据颜色值走不同的分支。后来我改了算法,把数据按分支条件预先排序,让同一个warp里的像素走同一个分支。性能直接翻了2.5倍。

2.4 三种并行的关系:GPU如何把它们串起来?

你可能会问:这三种并行在GPU里是怎么配合的?我画个简单的图给你看:

  • DLP:一个warp里的32个线程执行同一条指令,操作不同的数据。这是GPU最基本的并行单位。
  • TLP:多个warp在同一个SM上交替执行。当一个warp在等数据时,另一个warp立刻顶上。
  • ILP:GPU的ILP很有限,但也不是没有。比如一个warp里的指令可以流水线执行,或者通过双发射(dual-issue)同时执行一条整数指令和一条浮点指令。

说白了,GPU的设计哲学就是:用TLP掩盖延迟,用DLP提高吞吐,ILP只是锦上添花。这和CPU正好相反——CPU用ILP和Cache来降低延迟,TLP只是辅助。

一个小技巧:当你写CUDA kernel时,如果发现occupancy(占用率)很低,先别急着加线程。看看是不是每个线程用的寄存器太多了。寄存器用得多,能同时跑的warp就少,TLP就上不去。我曾经把一个kernel的寄存器用量从64降到32,occupancy从25%升到50%,性能反而提升了40%。

2.5 本章小结

这一章我们讲了三个基础概念:

  1. 冯·诺依曼架构:存储程序、顺序执行,但现代处理器都在“打破”这个顺序。
  2. 存储层次:从寄存器到DRAM,每一级都有它的脾气。Cache要利用局部性,DRAM要合并访问。
  3. 三种并行:ILP、TLP、DLP。GPU的核心是TLP+DLP,CPU的核心是ILP+Cache。

下一章,我们会正式进入GPU的微架构。到时候你会发现,今天讲的这些基础,每一个都会在GPU里找到对应的硬件实现。嗯,打好基础,后面才能跑得快。


公众号:蓝海资料掘金营,微信deep3321