Start From Cache
Index
每当谈论到计算机组成原理内容,或者是多线程下的线程同步,原子变量或者是锁等等内容,其根本都绕不开Cache等一些底层内容,今天就来探索一下Cache。
Cache 是什么?
Cache(高速缓冲存储器) 是 CPU 内部或紧挨着 CPU 的一层容量小、速度极快的存储器。
作用:自动保存主内存(DRAM)中最可能被 CPU 近期访问的数据副本。
如果没有Cache导致的性能噩梦!
如果有如下程序
int sum = 0;
for (int i = 0; i < N; i++) {
sum += array[i];
} // 我们前提规定 array是 int类型的。
前提
array是int类型,每个元素 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 的情况下是这样的:
一次迭代耗时 ≈ 内存访问时间 + 加法时间 ≈
总时间:
其中 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 次命中。
-
缺失率
每次迭代的平均访问时间:
总时间:
加速比:
仅仅加入一个 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了。