CPU很快、内存很慢;内存很快、硬盘很慢;硬盘很快、网络很慢,这个世界总归是不完美的。
Cache的作用是位于速度较快和较慢的两种硬件中,用于协调数据传输的速度差异。本文主要以CPU和内存间的cache为例进行讲解。
cache简介
缓存的结构及机制
CPU的缓存是完全由硬件电路控制的,不能显式对其进行控制,cache的物理结构如下所示:
其中,L1和L2cache为Core独有的,L3 Cache是一个cluster内共享的,L1 cache包括Data Cache(D Cache)和Instruction Cache(I Cache),下面我们讨论一下缓存的机制,
Cache经典问题
缓存一致性(重要)
一致性介绍
假设我们现在有四个CPU,每个CPU斗有自己的独立缓存,通过BUS与内存相连,数据流是单路的,也就是所有的核通过一条bus读取内存,假设每个核都读取了变量a
所在的内存,并进行了修改,那么每个核的私有缓存中保存的内容都是不一致的。所以我们引出了缓存一致性的问题,共享的数据资源在多个局部cache中被保存,所以我们需要维护一致性,当一个核更新了数据,如何保证其他的核上能够同步。所以缓存一致性是由于多个cache引起的
为了解决缓存一致性,我们先要了解cache的结构,cache内部实际上是一条条cache line或者cache block组成的,大小可能是64 Byte或者128 Byte
一致性协议
为了解决一致性带来的问题,计算机使用了总线窥视器监视总线上的读写动作,并根据一些协议修改缓存状态,这里我们介绍经典的snooping
协议,在该协议中,缓存行有四个状态,随着读写操作发生变化:
- 已修改Modified (M):缓存行与主存的值不同。如果别的CPU内核要读主存这块数据,该缓存行必须回写到主存,状态变为共享(S).
- 独占Exclusive (E):缓存行只在当前缓存中,但是干净的(clean)—缓存数据同于主存数据。当别的缓存读取它时,状态变为共享;当前写数据时,变为已修改状态。
- 共享Shared (S):缓存行也存在于其它缓存中且是干净的。缓存行可以在任意时刻抛弃。
- 无效Invalid (I):缓存行是无效的,需要从其他缓存或者内存中读取
MESI协议的状态转换图如下
举例:一个CPU A对变量a
进行写操作,还未写回memory中,此时状态为Modified,如果CPU B从内存中读取a
,得到的是脏数据,而CPU A能够监听到这个读操作,将a
的值写入内存,而CPU B由于未得到响应,会重新发起读请求,从memory中读到最新值。
初始状态 | 操作 | 响应 |
---|---|---|
Invalid(I) | PrRd | 给总线发BusRd信号 其他处理器看到BusRd,检查自己是否有有效的数据副本,通知发出请求的缓存 如果其他缓存有有效的副本,状态转换为(S)Shared,否则状态转换为(E)Exclusive 如果其他缓存有有效的副本, 其中一个缓存发出数据;否则从主存获得数据 |
PrWr | 给总线发BusRdX信号 状态转换为(M)Modified 如果其他缓存有有效的副本, 见到BusRdX信号后无效其副本 向缓存块中写入修改后的值 |
|
Exclusive(E) | PrRd | 无总线事务生成 状态保持不变 读操作为缓存命中 |
PrWr | 无总线事务生成 状态转换为(M)Modified 向缓存块中写入修改后的值PrWr |
|
Shared(S) | PrRd | 无总线事务生成 状态保持不变 读操作为缓存命中 |
PrWr | 发出总线事务BusUpgr 信号状态转换为(M)Modified 其他缓存看到BusUpgr总线信号,标记其副本为(I)Invalid. |
|
Modified(M) | PrRd | 无总线事务生成 状态保持不变 读操作为缓存命中 |
PrWr | 无总线事务生成 状态保持不变 写操作为缓存命中 |
缓存命中问题
当CPU申请访问数据,我们要考虑该数据是否已经缓存至cache中,这就是缓存的命中问题。缓存不命中会导致CPU直接访问内存数据,降低运行效率。
graph LR node1[Memory] node2[Cache] node3[CPU] node3--hit-->node2 node3--miss-->node1
缓存不命中发生条件
当发生以下情况时,缓存不命中
- 数据不在cache中
- 数据在cache中,但是被移除
如何提高缓存命中率
通过采用一些缓存管理策略,常见缓存管理策略如下:
- FIFO:先进先出
- LIFO:后进先出
- LRU:最近最少使用
- MRU:最近最多使用
关于Cache的一些术语
冷热缓存
冷缓存代表当前Cache没有保存任何有价值的值,不能提供性能加速;而热缓存代表已经存有一些有用的值,可以提高性能。相应地,我们向缓存中添加数据,就是预热的过程;而从缓存中清理数据,就是冷却的过程。