泠弦月屿 Rinzemoon
← Back to the beginning

论 Cache

组成原理
2026-05-30 文章 泠時月 4 分钟 1259 字
文件路径: content/posts/Build.md

Start From Cache

Index

每当谈论到计算机组成原理内容,或者是多线程下的线程同步,原子变量或者是锁等等内容,其根本都绕不开Cache等一些底层内容,今天就来探索一下Cache。

Cache 是什么?

Cache(高速缓冲存储器) 是 CPU 内部或紧挨着 CPU 的一层容量小、速度极快的存储器。 作用:自动保存主内存(DRAM)中最可能被 CPU 近期访问的数据副本。

如果没有Cache导致的性能噩梦!

如果有如下程序

c
int sum = 0;
for (int i = 0; i < N; i++) {
    sum += array[i];
} // 我们前提规定 array是 int类型的。

前提

  • arrayint 类型,每个元素 4 字节。
  • 数组长度 N=1 000 000N=1000000(约 4 MB)。
  • CPU 主频 3 GHz(时钟周期 ≈ 0.333 ns)。
  • 加法指令本身只需 1 个周期(0.333 ns)。
  • 内存(DRAM)访问延迟 tmem = 80 ns(约 240 个时钟周期)。

每次Loop都需要从内存里取出 array[i],在没有Cache 的情况下是这样的:

一次迭代耗时 ≈ 内存访问时间 + 加法时间 ≈

80+0.33380.33380+0.33380.333ns80+0.333≈80.33380+0.333≈80.333 ns。

总时间:

T(nocache)=N×80.333ns80.33msT(no cache)=N×80.333 ns≈80.33 ms

其中 99.6% 的时间都浪费在等待内存数据上。CPU 大部分时间处于“停顿”**(stall)**状态。

Cache的思想:局部性

Cache(高速缓冲存储器)是一个位于 CPU 内部或紧邻 CPU 的小容量、极快速的存储器。

它保存了一个内存中“最可能被频繁访问”的数据副本。

局部性类型 含义 在求和循环中的体现
时间局部性 刚访问的地址很可能很快再次访问 sum 变量在每次迭代中都被读写
空间局部性 访问某个地址后,其邻近地址也很快被访问 array[0], array[1], array[2] …… 连续访问

当CPU首次访问array[i] 的时候,Cache不仅会读取此元素,而且会把包含该元素的一块连续内存(称为缓存行,Cache Line) 整个取进来。

缓存行大小通常为 64 字节,可容纳 16 个 int

所以访问arr[0]的时候(简写为arr)了,arr[0]~arr[15] 都会被加载进Cache,随后访问此内容,会直接 Hit Cache,避免从DRAM读取内容了。

性能飞跃

假设情况: L1 数据缓存访问延迟 tL1=1 ns。

缓存行大小 L=64 字节,每个元素 4 字节 → 每行包含 K=16 个元素。

内存访问延迟 tmem = 80 ns。

对于求和循环:

  • 每 16 次迭代发生一次 Cache 缺失(第一次访问该行),其余 15 次命中。

  • 缺失率

    missrate=1/16=0.0625miss rate=1/16=0.0625

每次迭代的平均访问时间:

tavg=hit rate×tL1+miss rate×tmem=1516×1ns+116×80ns=0.9375+5=5.9375nst_{\text{avg}} = \text{hit rate} \times t_{\text{L1}} + \text{miss rate} \times t_{\text{mem}} = \frac{15}{16} \times 1\,\text{ns} + \frac{1}{16} \times 80\,\text{ns} = 0.9375 + 5 = 5.9375\,\text{ns}

总时间:

Twith L1=N×tavg106×5.9375 ns=5.94 msT_{\text{with L1}} = N \times t_{\text{avg}} \approx 10^6 \times 5.9375\ \text{ns} = 5.94\ \text{ms}

加速比

Twith L1Tno cache5.9480.3313.5 倍\frac{T_{\text{with L1}}}{T_{\text{no cache}}} \approx \frac{5.94}{80.33} \approx 13.5\ \text{倍}

仅仅加入一个 L1 Cache,就让相同循环快了 13 倍以上,如若有多级缓存,效率还会增加。

过程流水线

典型的三级缓存结构

现代 CPU 通常包含 L1、L2、L3 三级缓存,外加主存(DRAM)和磁盘。

级别 典型容量(每核心) 访问延迟 归属 特点
L1 32 KiB(指令)+ 32 KiB(数据) ~1 ns (3-4 周期) 每核心私有 最快,容量最小,分离指令/数据
L2 256 KiB ~ 1.25 MiB ~3-5 ns (10-12 周期) 每核心私有 统一缓存,速度与容量折中
L3 2 MiB ~ 36 MiB ~10-15 ns (30-40 周期) 多核共享 最后一级缓存(LLC),最大
主存 8 GB ~ 512 GB ~80 ns 系统共享 慢速,容量巨大

数据再层次下的数据流动

缓存一致性

在现代多核CPU架构中,每个核心都有其L1 or L2 缓存,如果两个核心分别执行求和循环***前文的循环***的两个不同部分,可能会出现同一缓存行被两个核心同时缓存的情况。

**In Order to **保证数据一致,硬件实现 MESI 协议。每个缓存行有四种状态:

  • M(Modified):本核心独有,已修改,与内存不一致。
  • E(Exclusive):本核心独有,与内存一致。
  • S(Shared):多个核心共享,与内存一致。
  • I(Invalid):无效。

Core0 修改 arr[i] 时,它会通过总线发送**“读(Read)独占”请求,强制其他核心将该缓存行置为Invalid**。这样 Core1 再读该行时就会发生缺失,重新从内存或 Core0 获取最新值。

这正是 伪共享(False Sharing) 问题的根源:如果两个无关变量在同一缓存行,即使只修改其中一个,也会导致整行在其他核心失效。

Fin

暂时就讲一部分吧,如果有时间记录的话下一期继续。 把它做成一期 Series了。

© 泠時月 2026,采用 CC BY 4.0 许可,转载保留署名。

留言 · 0 段对话

扫码分享

二维码