1.1 一次访存到底有多贵
建立「访存比计算贵」的量化直觉,并区分延迟与带宽
学习目标
读完这一节,你应该能够:
- 说出从寄存器到内存、从内存到磁盘,各级存储的代价大约差多少个数量级。
- 用自己的话解释延迟和带宽的区别,并说出各自的瓶颈场景。
- 解释为什么"优化程序"经常等于"减少搬运"而不是"减少运算"。
一、一个反直觉的事实
大多数人写代码时形成的直觉是:计算很贵,读内存很便宜——不就是读个变量吗?
在现代计算机上,这个直觉是反的。
要看清这一点,需要观察两条曲线在过去几十年里是怎么分岔的:
- CPU 的运算能力,随着工艺进步、流水线加深、超标量发射、SIMD 向量化和多核,持续快速提升。
- 内存的访问延迟,主要由 DRAM 的物理特性决定,几十年来只从一百纳秒量级缓慢改善到几十纳秒量级。
两条曲线的差距越拉越大。这个缺口在 1990 年代就有了名字:存储墙(memory wall)。
后果非常直接:CPU 经常在空等内存。
这里不需要记住具体年份和倍数。要记住的是趋势的形状——运算能力在指数增长,而访存延迟几乎是平的。所有后面的结论都从这个形状推出来。
二、用两把尺子量存储层次
尺子一:周期数
CPU 内部的一切以时钟周期为单位。现代处理器的时钟频率大约是每秒 3 至 5 个吉周期(GHz),所以:
下面是一张典型量级的表。注意:具体数字随微架构变化,不同机器之间可能相差 2 至 3 倍,但量级关系是稳定的——而量级关系才是你要记住的东西。
| 存储层级 | 典型延迟 | 约合周期数 |
|---|---|---|
| 寄存器 | 几乎为零 | 0 |
| L1 cache | 约 1 纳秒 | 3 – 5 |
| L2 cache | 约 4 纳秒 | 10 – 15 |
| L3 cache | 约 15 – 30 纳秒 | 40 – 90 |
| 主存(DRAM) | 约 60 – 100 纳秒 | 200 – 300 |
| NVMe SSD | 约 100 微秒 | 约 30 万 |
| 机械硬盘 | 约 10 毫秒 | 约 3000 万 |
| 同一机房内网络往返 | 约 0.5 毫秒 | 约 150 万 |
| 跨洲网络往返 | 约 150 毫秒 | 约 5 亿 |
尺子二:比例化时间
周期数对人不直观。一个更有效的方法是把 CPU 的一个周期放大成 1 秒,然后看看每一级存储对应多长时间。
按这个尺度(相当于把时间放慢约 33 亿倍):
| 存储层级 | 如果 1 个周期 = 1 秒 |
|---|---|
| 寄存器 | 0 秒 |
| L1 cache | 4 秒 |
| L2 cache | 13 秒 |
| L3 cache | 约 1 分钟 |
| 主存(DRAM) | 约 4.5 分钟 |
| NVMe SSD | 约 4 天 |
| 机械硬盘 | 约 1 年 |
| 跨洲网络 | 约 15 年 |
💡 这张表是本节最该记住的东西。
它说明:如果一次 L1 命中是"看一眼桌子上的便利贴",那么一次主存访问就是"下楼去隔壁楼的档案室,而且档案员现在不在座位上"。你会毫不犹豫地在便利贴上多写几遍,来避免跑那一趟——这正是 Cache 和分块优化的全部动机。
三、延迟与带宽是两件事
这两个词经常被混用,但它们是不同的物理量,在不同场景下成为不同的瓶颈。
| 延迟(latency) | 带宽(bandwidth) | |
|---|---|---|
| 含义 | 一次请求从发出到拿到数据要多久 | 单位时间内能搬运多少字节 |
| 单位 | 纳秒 | GB/s |
| 类比 | 一辆货车跑一趟要多久 | 有多少条车道、车跑多快 |
| 什么时候是瓶颈 | 请求次数少、每次都要等(串行依赖) | 数据量大、且能并发搬运 |
关键洞察:延迟和带宽是可以分开优化的。
- 如果瓶颈是延迟,解决办法通常是并发:一次发很多个请求,让等待时间重叠起来。CPU 用乱序执行和预取做到这一点,GPU 用海量线程切换做到这一点。
- 如果瓶颈是带宽,解决办法是减少要搬的字节数:压缩、量化、复用、或者干脆别搬。
⚠️ 常见误区:看到"内存慢"就以为要减少访问次数。但如果数据量很大且访问是独立的,减少次数并不解决问题——真正的瓶颈是带宽。第 1.5 节会用这个区分来判断 GPU 上的场景。
四、算一笔账:为什么"访存比计算贵"
现在把两个事实放在一起:
- CPU 每个周期可以完成多次浮点运算(超标量发射 + SIMD 向量化,一个周期处理 4 到 16 个单精度浮点数很常见)。
- 从主存取回一个 4 字节浮点数,需要几百个周期。
也就是说,为了让 CPU 的运算单元不闲着,每搬来 4 个字节,就得让它做几十甚至上百次运算。
这个比值有一个正式的名字:计算强度(arithmetic intensity)。
计算强度越高,程序越"划算"——因为搬运的代价被更多的计算摊薄了。计算强度低的程序,无论 CPU 多快,性能都被访存卡死。
这就解释了为什么现代程序的优化重点常常是"减少搬运"而不是"减少运算":减少运算只能让本来就空闲的运算单元更空闲,而减少搬运直接去掉了瓶颈。
五、与 AI Infra 的连线
这一节的全部内容,就是为了让你在下一次看到下面这些说法时,知道它们在说什么。
1. 为什么大模型推理的 decode 阶段特别慢?
生成一个 token 时,模型要把全部权重从显存里读一遍,而每个权重只参与很少的运算。计算强度大约只有 1 到 2——远低于硬件的"划算线"。所以 decode 阶段是纯粹的带宽受限:GPU 的运算单元绝大多数时间在等显存。
这就是后面量化、投机解码、MoE 这些技术存在的根本原因——它们全都建立在"decode 卡在带宽上,算力是富余的"这个判断上。
2. 为什么 GPU 的显存带宽是核心指标?
一块数据中心 GPU 的算力和显存带宽之比,决定了它以多高的计算强度才能跑满。这个比值就是后面 1.5 节要讲的 Roofline 拐点。买 GPU 时只看算力(TFLOPS)而不看带宽,是新手最常见的错误。
3. 为什么"减少通信"在分布式训练里是头等大事?
因为跨卡通信的延迟和带宽代价,比访问本机显存还要高出好几个数量级。第四篇里所有的并行策略,本质上都是在"多算一点"和"少传一点"之间做权衡。
关键结论
- 运算能力在指数增长,访存延迟几乎是平的,所以 CPU 经常在等内存。
- 各级存储的代价相差几个数量级:L1 是秒级,主存是分钟级,磁盘是年级(按 1 周期 = 1 秒的比例化尺度)。
- 延迟和带宽是两件事:延迟靠并发隐藏,带宽靠减少数据量解决。
- 计算强度决定一个程序是否划算。计算强度低,再快的算力也没用。
- 因此,优化常常是减少搬运而不是减少运算。
自测问题
- 按"1 个周期 = 1 秒"的尺度,一次主存访问大约相当于多长时间?这个尺度下,一次 L1 命中呢?
- 延迟和带宽分别在什么情况下成为瓶颈?各举一个例子。
- 一个程序的计算强度是 1 FLOP/Byte。如果硬件的算力是 100 TFLOPS、带宽是 2 TB/s,这个程序能跑到多少性能?它受限在哪一侧?
- 用你自己的话解释:为什么"把循环里的重复计算挪到循环外"能提升性能?(提示:从搬运的角度想,不一定要从运算的角度想。)
- 有人告诉你"这个 kernel 慢是因为内存访问次数太多"。你需要追问哪两个问题才能判断他说得对不对?
延伸阅读
- 教材:Computer Systems: A Programmer's Perspective(CSAPP)第 6 章开头对存储层次与技术趋势的讨论。本站的讲解是重新组织的,教材里更完整的背景数据和图表值得对照看。
- 原始概念:Wulf 与 McKee 在 1995 年提出"存储墙"的那篇文章(搜索 "Hitting the Memory Wall: Implications of the Obvious")。
下一节 → 1.2 存储层次与局部性