LabHub
学习 学习路径 课程

计算机组成

内存层次 — 快的小,大的慢

在 LabHub 中继续学习

一句话总结

内存无法同时做到又快、又大、又便宜,因此系统把它从小而快到大而慢逐层排列,并将常用数据提升到上层。

分层图: 内存墙(memory wall) · 数量级差异 · 局部性(locality) · 时间局部性

为什么需要它

寄存器能以和 CPU 相同的速度工作,却只有几十个。DRAM 可以容纳数十 GB 数据,但一次访问要耗费数百个周期。这道鸿沟不仅没有缩小,反而随着每一代硬件继续扩大。增加运算单元只需放入更多晶体管,而在芯片内外搬运数据的能力,却受到引脚数量和布线等物理限制。

这种不断累积的差距称为内存墙(memory wall)。层次结构是绕过这堵墙唯一现实的方法。

它如何工作

典型的层次结构及其大致延迟如下。具体数字会随硬件世代变化,但重点在于它们之间的数量级差异

层级 容量 访问延迟(大致)
寄存器 数百字节 不到 1 个周期
L1 缓存 32~64KB 4~5 个周期
L2 缓存 0.5~2MB 12~20 个周期
L3 缓存 数十 MB 40~70 个周期
DRAM 数十 GB 200~300 个周期
NVMe SSD 数 TB 数万个周期

这种结构之所以真正有效,是因为程序具有局部性(locality)

缓存直接利用了这两种特性。它不会逐字节取数据,而是以缓存行为单位,通常一次取出完整的 64 字节。即使只读取一个 4 字节整数,也会连同相邻的 15 个整数一起载入。这就是按顺序遍历数组的代码更快的原因。

把缓存未命中分为三类,才能分别采取合适的对策。

实际工作中的表现

对于按行优先存储的二维数组,如果改为按列优先遍历,即使运算完全相同,也可能慢数倍乃至数十倍。因为每次访问都要载入一条新的缓存行,却只使用其中 4 个字节便将其丢弃。矩阵乘法采用分块(tiling/blocking)的原因也相同:把工作集切成能放进缓存的大小,从而增加复用。

这个问题在 AI 工作负载中表现得更加直观。LLM 推理的解码阶段会读取庞大的权重,与一个很小的输入相乘后便将其丢弃。它的算术强度(每读取一个字节所执行的运算数)极低,因此运算器大部分时间都在闲置,性能由内存带宽决定。人们常抱怨 GPU 利用率达不到 30%,根源往往就在这里。此时购买计算能力更强的芯片只是白花钱;正确做法是减少读取的字节数(量化),或提高数据复用。

接下来的测验将检查什么

检查你能否解释:为什么缓存行每次移动 64 字节;以及为什么同一种算法仅仅改变访问顺序,就会慢上数倍。