多级缓存
为什么需要 CPU cache?
CPU 的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU 常常需要等待主存,浪费资源,所以 cache 的出现,是为了缓解 CPU 和内存之间速度的不匹配问题(结构:cpu->cache->memort)
cache 工作原理
cache 的工作原理是基于“局部性”原理,它包含以下两个方面:
- 时间局部性:如果某个数据被访问,那么在不久的将来他很可能被再次访问
- 空间局部性:如果某个数据被访问,那么与他相邻的数据很快也可能被访问
多级缓存是什么
左图为最简单的高速缓存的配置,数据的读取和存储都经过高速缓存,CPU 核心与高速缓存有一条特殊的快速通道;主存和高速缓存都连在系统总线上,这条总线还用于其他组件的通信。
高速缓存出现不久,系统变得越来越复杂,高速缓存与主存之间的速度差异被拉大,直到加入了另一级缓存,新加入的这级缓存比第一缓存更大,而且更慢,而且经济上不合适,所以有了二级缓存,甚至是三级缓存。
cache 带来的问题
cache 给系统带来性能上飞跃的同时,也引入了新的问题“缓存一致性问题”。设想如下场景(cpu 一共有两个核,core1 和 core2):
以 i++为例,i 的初始值是 0.那么在开始每个核都存储了 i 的值 0,当第 core1 块做 i++的时候,其缓存中的值变成了 1,即使马上回写到主内存,那么在回写之后 core2 缓存中的 i 值依然是 0,其执行 i++,回写到内存就会覆盖第一块内核的操作,使得最终的结果是 1,而不是预期中的 2。
缓存一致性
为了达到数据访问的一致,需要各个处理器在访问缓存时遵循一些协议,在读写时根据协议来操作,比较经典的 Cache 一致性协议当属 MESI 协议,很多其他的处理器都是使用它的变种。
单核 Cache 中每个 Cache line 有 2 个标志:dirty 和 valid 标志,它们很好的描述了 Cache 和 Memory(内存)之间的数据关系(数据是否有效,数据是否被修改),而在多核处理器中,多个核会共享一些数据,MESI 协议就包含了描述共享的状态。
MESI 协议
在 MESI 协议中,每个 Cache line 有 4 个状态,可用 2 个 bit 表示,它们分别是:
状态 | 描述 |
---|---|
M(Modified) | 这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本 Cache 中。 |
E(Exclusive) | 这行数据有效,数据和内存中的数据一致,数据只存在于本 Cache 中。 |
S(Shared) | 这行数据有效,数据和内存中的数据一致,数据存在于很多 Cache 中。 |
I(Invalid) | 这行数据无效。 |
- M(Modified)和 E(Exclusive)状态的 Cache line,数据是独有的,不同点在于 M 状态的数据是 dirty 的(和内存的不一致),E 状态的数据是 clean 的(和内存的一致)。
- S(Shared)状态的 Cache line,数据和其他 Core 的 Cache 共享。只有 clean 的数据才能被多个 Cache 共享。
- I(Invalid)表示这个 Cache line 无效。
E 状态示例如下:
只有 Core 0 访问变量 x,它的 Cache line 状态为 E(Exclusive)。
S 状态示例如下:
3 个 Core 都访问变量 x,它们对应的 Cache line 为 S(Shared)状态。
M 状态和 I 状态示例如下:
Core 0 修改了 x 的值之后,这个 Cache line 变成了 M(Modified)状态,其他 Core 对应的 Cache line 变成了 I(Invalid)状态。
状态迁移
在 MESI 协议中,每个 Cache 的 Cache 控制器不仅知道自己的读写操作,而且也监听(snoop)其它 Cache 的读写操作。每个 Cache line 所处的状态根据本核和其它核的读写操作在 4 个状态间进行迁移。
MESI 协议状态迁移图如下:
- Local Read 表示本内核读本 Cache 中的值
- Local Write 表示本内核写本 Cache 中的值
- Remote Read 表示其它内核读其它 Cache 中的值
- Remote Write 表示其它内核写其它 Cache 中的值
- 箭头表示本 Cache line 状态的迁移
- 环形箭头表示状态不变
当内核需要访问的数据不在本 Cache 中,而其它 Cache 有这份数据的备份时,本 Cache 既可以从内存中导入数据,也可以从其它 Cache 中导入数据,不同的处理器会有不同的选择。MESI 协议为了使自己更加通用,没有定义这些细节,只定义了状态之间的迁移,下面的描述假设本 Cache 从内存中导入数据。
MESI 状态之间的迁移过程如下:
当前状态 | 事件 | 行为 | 下一个状态 |
I(Invalid) | Local Read | 如果其它Cache没有这份数据,本Cache从内存中取数据,Cache line状态变成E; 如果其它Cache有这份数据,且状态为M,则将数据更新到内存,本Cache再从内存中取数据,2个Cache 的Cache line状态都变成S; 如果其它Cache有这份数据,且状态为S或者E,本Cache从内存中取数据,这些Cache 的Cache line状态都变成S | E/S |
Local Write | 从内存中取数据,在Cache中修改,状态变成M; 如果其它Cache有这份数据,且状态为M,则要先将数据更新到内存; 如果其它Cache有这份数据,则其它Cache的Cache line状态变成I | M | |
Remote Read | 既然是Invalid,别的核的操作与它无关 | I | |
Remote Write | 既然是Invalid,别的核的操作与它无关 | I | |
E(Exclusive) | Local Read | 从Cache中取数据,状态不变 | E |
Local Write | 修改Cache中的数据,状态变成M | M | |
Remote Read | 数据和其它核共用,状态变成了S | S | |
Remote Write | 数据被修改,本Cache line不能再使用,状态变成I | I | |
S(Shared) | Local Read | 从Cache中取数据,状态不变 | S |
Local Write | 修改Cache中的数据,状态变成M, 其它核共享的Cache line状态变成I | M | |
Remote Read | 状态不变 | S | |
Remote Write | 数据被修改,本Cache line不能再使用,状态变成I | I | |
M(Modified) | Local Read | 从Cache中取数据,状态不变 | M |
Local Write | 修改Cache中的数据,状态不变 | M | |
Remote Read | 这行数据被写到内存中,使其它核能使用到最新的数据,状态变成S | S | |
Remote Write | 这行数据被写到内存中,使其它核能使用到最新的数据,由于其它核会修改这行数据, 状态变成I | I |