06 - 为什么要有寄存器和多级缓存
上一章留了个问题:CPU 快、内存慢,CPU 每次取数据都得干等内存,太亏了。这一章讲清楚硬件是怎么解决这个「快慢不匹配」的——答案又是那个熟悉的思想:分层 + 缓存。
问题有多严重
打个直观的比方:如果 CPU 做一次计算只要「1 秒」,那么它去内存取一次数据,大约要等「几分钟」。也就是说,CPU 算得再快,也常常在干等内存送数据。这就像一个手速极快的大厨,食材却要从很远的仓库一趟趟搬,大部分时间都浪费在等食材上。
解决思路很自然:在 CPU 和内存之间,塞几层「离 CPU 更近、更快」的小仓库,把常用的数据提前放进去。 这就是存储金字塔在硬件里的落地。
硬件里的存储层级
从离 CPU 最近(最快最小)到最远(最慢最大):
┌──────────────┐ 最快、最小、最贵
│ 寄存器 │ 在 CPU 核心里,几十个,装正在算的数
├──────────────┤
│ L1 缓存 │ 每个核私有,很小很快
│ L2 缓存 │ 稍大稍慢
│ L3 缓存 │ 多核共享,更大更慢
├──────────────┤
│ 内存 (RAM) │ 大很多,但慢很多
└──────────────┘ 最慢、最大、最便宜
寄存器(Register)
CPU 内部最快的存储,就在运算单元旁边,但数量极少(几十个)、每个只能装一点点。CPU 正在计算的那几个数,就临时放在寄存器里,存取几乎不花时间。
相当于大厨手边案板上正在切的那几样菜——伸手就够到。
CPU 缓存(Cache,分 L1/L2/L3)
寄存器太少了,装不下稍多的数据。于是在寄存器和内存之间,加了几层缓存,用的是比内存更快的材料。
- L1:最小最快,每个 CPU 核独享。
- L2:大一点、慢一点。
- L3:更大、更慢,通常多个核共享。
相当于厨房里离灶台近的小料理台——比跑仓库快多了,放着这道菜大概率会用到的食材。
CPU 取数据的流程
CPU 要一个数据时,从近到远一层层问:
寄存器有吗? → 有,直接用(最快)
│没有
▼
L1 有吗? → 有,拿来(很快)
│没有
▼
L2 / L3 有吗? → 有,拿来(较快)
│没有
▼
去内存拿(慢),顺便复制一份到缓存里,赌待会儿还用
- 在缓存里找到了,叫命中(Cache Hit),飞快。
- 没找到得去下一层,叫未命中(Cache Miss),慢,但拿回来时会顺手缓存一份。
为什么缓存能生效:局部性
缓存这套「赌你待会还用」的策略之所以有效,靠的是程序访问数据的局部性规律(操作系统篇第 10 章讲过,这里再强调,因为它太重要了):
- 时间局部性:刚用过的数据,很可能马上又用(比如循环里反复用的变量)。
- 空间局部性:用了某个数据,它旁边的数据也很可能马上用(比如挨个遍历数组)。
正因如此,CPU 从内存取数据时,往往不是只取你要的那一个,而是把它连同旁边一小块(叫「缓存行」)一起搬进缓存,赌你接下来会用到旁边的。
这就是为什么「顺序访问数组」比「东一下西一下地乱访问」快得多——顺序访问对缓存友好,命中率高。写高性能代码时,这是个重要直觉。
小结
- CPU 远快于内存,直接取内存会让 CPU 大量时间干等,所以在中间加了几层更快的存储。
- 硬件存储层级(快→慢):寄存器 → L1/L2/L3 缓存 → 内存,越近越快越小越贵。
- 寄存器装正在算的数(手边案板);缓存装大概率要用的数据(近处料理台)。
- CPU 取数据逐层查找,找到叫命中(快),没找到叫未命中(慢,但会顺手缓存)。
- 缓存靠局部性(时间/空间)生效,还会连带旁边一整块一起缓存——所以顺序访问更快。
下一章 → 07 - 时钟、指令、流水线:CPU 为什么越来越快 | 回到 README 目录