2. 内存层次结构:从寄存器到NUMA
大家好,我是你们的老朋友。今天我们来聊聊内存层次结构。这玩意儿说白了,就是计算机的「存储金字塔」。你想想看,CPU 跑得飞快,内存却慢得像蜗牛。怎么解决?加缓存呗。
我个人习惯把内存层次比作一个团队:寄存器是老板的随身笔记本,L1 缓存是秘书的桌面,L2 是文件柜,L3 是档案室,主存就是隔壁的图书馆。 越靠近 CPU,速度越快,但容量越小,价格也越贵。
2.1 寄存器:CPU 的贴身侍卫
寄存器是离 CPU 最近、速度最快的存储。它就在 CPU 内部,跟运算单元直接相连。访问它只需要 1 个 CPU 时钟周期,几乎没有延迟。
但寄存器有个硬伤——数量极少。x86 架构下,通用寄存器也就十几个。ARM 稍微多点,但也就 30 多个。所以编译器会拼命优化,把最常用的变量塞进寄存器。
2.2 L1/L2/L3 缓存:速度与容量的博弈
缓存是 CPU 和主存之间的「缓冲带」。为什么需要多层?因为单层缓存要么太快但太小,要么太大但太慢。
| 缓存层级 | 典型大小 | 访问延迟 | 位置 |
|---|---|---|---|
| L1 | 32KB - 64KB | 2-4 周期 | 每个核心独享 |
| L2 | 256KB - 1MB | 10-20 周期 | 每个核心独享 |
| L3 | 8MB - 32MB | 30-50 周期 | 所有核心共享 |
嗯,这里要注意:L1 缓存又分为指令缓存和数据缓存。我见过不少新手写代码,循环体太大,指令缓存装不下,结果 CPU 一直在等指令加载——这叫「指令缓存缺失」,性能直接腰斩。
2.3 主存:最后的「大仓库」
当缓存都找不到数据时,CPU 只能去主存(DRAM)里找。主存容量大(GB 级别),但速度慢——访问延迟在 100-300 个时钟周期之间。
为什么这么慢?因为 DRAM 需要行激活、列选择、预充电等一系列操作。而且数据要通过内存总线传输,这本身就有物理延迟。
我个人习惯把主存看作「最后的手段」。写高性能代码时,我会尽量让工作集(working set)塞进 L2 甚至 L1 缓存。如果实在塞不下,那就优化数据访问模式,让 CPU 能预取。
2.4 NUMA 架构:多核时代的「内存距离」
NUMA(非统一内存访问)是现代多路服务器的主流架构。简单说:每个 CPU 有自己的本地内存,访问本地内存快,访问远程内存慢。
为什么会这样?因为 CPU 和内存之间通过 QPI/UPI 总线连接。访问远程内存需要跨总线,延迟翻倍甚至更多。
我记得有一次帮客户调优数据库,发现某个节点内存带宽只有理论值的 30%。排查后发现,操作系统把内存分配到了远端 NUMA 节点上。解决方案是绑定 CPU 亲和性,并设置内存分配策略为 localalloc。带宽直接翻倍。
2.5 存储层级访问时间对比
下面这张表,我建议你打印出来贴在工位上。它直观展示了「为什么缓存这么重要」。
| 存储层级 | 典型延迟 | 相对比例 |
|---|---|---|
| 寄存器 | 0.3 ns | 1x |
| L1 缓存 | 0.9 ns | 3x |
| L2 缓存 | 3 ns | 10x |
| L3 缓存 | 10 ns | 33x |
| 主存 (本地) | 100 ns | 333x |
| 主存 (远程) | 180 ns | 600x |
| SSD (NVMe) | 10,000 ns | 33,333x |
你想想看,从寄存器到主存,延迟差了 300 多倍。这意味着:一次主存访问的时间,CPU 可以执行几百条指令。 所以,缓存缺失才是性能杀手,而不是 CPU 不够快。
2.6 知识体系总览
下面这张图,是我自己画的存储层次结构图。它把整个体系串起来了。
这张图里,越往上走,速度越快,容量越小。NUMA 架构下,主存还被分成了「本地」和「远程」两块。我建议你写代码时,时刻想着:我的数据现在在金字塔的哪一层?
perf stat -e cache-references,cache-misses,cycles,instructions ./your_program。如果缓存缺失率超过 5%,就该优化了。
好了,这一章就到这里。记住:理解内存层次结构,是性能优化的第一步。 下一章我们聊聊缓存一致性协议——那才是真正烧脑的东西。