计算机组成原理_CPU缓存
2025-01-22 08:19:30    3.2k 字   
This post is also available in English and alternative languages.

本篇着重关注 存储器层次结构 中被称为「高速缓存」的几层,即:CPU Cache,部分内容与 计算机组成原理_存储器 重合。


存储器层次结构

众所周知,CPU 的性能、处理速度非常快。目前计算机系统中,普通存储器(DRAM内存、磁盘)的性能是严重滞后于 CPU 性能的,而且这种差距还在增大,为了提高 CPU 与存储器的交互速度,在它们之间插入一个更小更快的存储设备的想法已经成为一个普遍观念,实际上每个计算机系统中的存储设备都被组织成了一个 存储器层次结构

存储器层次结构 由不同速度、容量的多级存储器构成;距离 CPU 越近,存储器容量越小、访问速度越快,同样价格越贵;距离 CPU 越远,存储器容量越大、访问速度越慢,但价格便宜。


1. 结论先行

本篇核心内容:

MESI协议是一种写失效缓存;
MESI是Modified(已修改)Exclusive(独占)Shared(共享)Invalid(失效) 的首字母缩写,代表缓存行(cache line)的四种状态,任何多核系统中,缓存行都处于这四种状态之一。

  • 独占(Exclusive)缓存行
    在’独占’状态下的缓存行,如果收到来自总线的读取请求,这个缓存行就会变成’共享’状态。

  • 已修改(Modified)缓存行
    core的写操作只能在缓存行状态是已修改独占时可以自由执行(如果没有独占,同一份数据存在于多个core的缓存中,不能直接修改)。
    如果在缓存行是共享状态,core要先发送一条"我要独占"的请求给总线,总线会通知其他所有core(向其他所有的core广播一个请求),要求其他core先把它们对应缓存中的缓存行标识为失效状态(如果它们有的话);

  • 失效(Invalid)缓存行
    这种状态的缓存行会被忽略; 一旦缓存行被标识为’失效’,那对于CPU来说,等同于它从来没被加载到缓存中。

  • 共享(Shared)缓存行
    和主存数据保持一致的拷贝,这种状态下的缓存行可以在任意时刻抛弃。
    对于共享状态的缓存行,如果其他core有独占广播,并且命中,则会将该缓存行状态置为失效状态


2. 术语约定

  1. CPU = 中央处理器 = 处理器

  2. SRAM = Static Random-Access Memory = 静态RAM = 静态随机存取存储器

  3. DRAM = Dynamic Random Access Memory = 动态RAM = 动态随机存取存储器

  4. 主存 = Main Memory = 多个DRAM内存模块组成 = 内存条

  5. core = CPU的一个核心

注意:以下内容中,使用"缓存" 代指 “高速缓存”。


3. CPU缓存

CPU缓存是用于减少CPU访问内存所需平均时间的存储器,是位于CPU与主存之间的临时数据交换器。

CPU缓存一般直接与CPU芯片集成或位于总线互联的独立芯片上。

L1高速缓存(一级缓存):最靠近CPU寄存器的一个SRAM高速缓存存储器,访问速度大约是4个时钟周期,几乎和CPU寄存器一样快。

L2高速缓存(二级缓存):在L1高速缓存与主存之间的一个SRAM高速缓存存储器,存储空间比L1略大,访问速度大约是10个时钟周期

有些高性能CPU,还增加了一个更大的高速缓存,称为L3高速缓存(三级缓存),访问速度大约是50个时钟周期

CPU缓存主要由SRAM(Static Random-Access Memory)静态随机存取存储器构成,相较于主存(DRAM,平时的内存条),SRAM速度更快,价格更高。


4. 缓存通用结构

缓存由多个缓存组(cache set)构成,每个缓存组包含一个或E个缓存行(cache line),每行包含一个有效位、一些标记位、一个数据块(block)

  • 块是一个固定大小的数据包,在缓存与主存(下一层缓存)之间来回传送。
  • 行是缓存中的一个容器,用来存储块及其他信息(有效位、标记位)。
  • 组是一个或多个行的集合。
高速缓存的通用组织(来源:《深入理解计算机系统》(第3版))

5. 分类

根据每个缓存组包含的缓存行数量,缓存可以分为以下种类:

  • 直接映射缓存(direct mapped cache):包含多组,每组只有一行(E = 1)

  • 组相连缓存(set associative cache):包含多组,每组有多行(E > 1),也称为E路组相连缓存

  • 全相联缓存(full associative cache):只有一组,一组包含所有行。

直接映射缓存中的组只由一行组成;组相连缓存全相联缓存中的组由多个行组成。
直接映射缓存中,组和行实际上是等价的;但在相联缓存中,组和行是不一样的。


6. 关于读、写

读、写的策略,不同的操作系统、芯片有不同的实现方式,粗浅的了解下

6.1. 读

缓存关于读的操作比较简单;

  1. 当CPU执行读内存的指令时,向缓存请求(在缓存中查找所需数据的副本)。
  2. 如果命中,立即将数据副本返回给CPU。
  3. 如果没命中,则向下一层(存储器层次结构中较低层)请求数据,将返回的数据块存储到某个缓存行中,然后再返回CPU。

6.2. 写

写的情况较为复杂,假设CPU需要写一个已经缓存了的数据(写命中,write hit),怎么更新此数据在存储器层次结构中低一层的副本?

有两种写的策略:回写(write-back)直写(write-through)

  • 回写(write-back)
    尽可能的推迟更新,只有当替换算法要驱逐’过时块’时,才把它写到低一层的缓存中。如果一个数据在被回写到内存之前从未被写入过(更新),则可以免去回写操作。
    这种策略,能节省大量写操作、显著减少总线流量,但缺点是增加了复杂性。缓存必须为每个缓存行维护一个额外的修改位(dirty bit),表明这个缓存块是否被修改过。

  • 直写(write-through)
    最简单的方式,立即将对应修改的数据写回到低一层的缓存中。
    此种方式虽然简单,但缺点是,每次都会引起总线流量

如果CPU要写的数据没有在缓存中(写不命中),需要通过**写分配(write-allocate)**进行操作:首先从低一层的缓存中加载数据块,然后更新这个缓存块。写分配的缺点是每次不命中都会导致一个块从低一层传送到上一层缓存。

另一种方法,非写分配(not write allocate):避开缓存,直接把数据写到低一层的缓存中。

直写(write-through)通常采用非写分配;回写(write-back)通常采用写分配


7. 真实缓存层次结构

如下图,Intel Core i7处理器的缓存层次结构。

每个CPU芯片包含四个核(Core),每个核有自己私有的L1、L2缓存,所有核共享芯片上的L3缓存。

这种设计的特性是,所有的SRAM高速缓存存储器都在CPU芯片上。

真实高速缓存层次结构(来源:《深入理解计算机系统》(第3版))

8. 缓存一致性(Cache Coherency)

如果CPU只有一个core,那自然不会出现一致性问题;

但如上图那样,CPU中有多个core,每个core都有自己的L1、L2缓存,那就会遇到缓存一致性的问题:同一数据的多个副本可能同时存在于不同core的缓存中。如果允许CPU自由地修改它们自己的副本,就会导致不同core的缓存对存储器中同一数据的反映不一致。

我们希望的是,当某个core修改了数据时,其他core可以收到通知,保持数据的一致性。

注意,一致性问题的根源是CPU多core,每个core都有自己的缓存导致的

早期时候,通过给总线加锁的方式来解决一致性问题。就是说,同一时间,只有一个core能操作内存数据,这样做的缺点很明显,总线加锁期间其他core无法访问内存,效率低下。

因此出现第二种解决方案,通过**缓存一致性协议(Coherency protocols)**解决问题。


8.1. 总线嗅探

总线嗅探(Bus Snooping),本质上是把所有的读写操作通过总线(Bus)广播给所有的CPU核心(core),然后让各个core各自嗅探这些请求,根据本地情况进行响应。


8.2. MESI

基于总线嗅探机制,可以分为很多种不同的缓存一致性协议,最常用的是:MESI协议。

MESI协议也是一种**写失效(write Invalidate)**协议,MESI 是Modified(已修改)Exclusive(独占)Shared(共享)Invalid(失效) 的首字母缩写,代表缓存行(cache line)的四种状态,任何多核系统中,缓存行都处于这四种状态之一。


8.2.1. Exclusive(独占)缓存行

缓存行数据和主存保持一致,并且缓存行只在一个core中(其他core不能同时持有它);

当别的core要读取它时,状态变为共享;当core写(修改)后,状态变为已修改

在’独占’状态下的缓存行,如果收到来自总线的读取请求,这个缓存行就会变成’共享’状态。这个共享状态是因为,另一个core也把对应的缓存行从内存中加载到了自己的缓存中。


8.2.2. Modified(已修改)缓存行

缓存行中数据与主存中的数据不同,被所属的core修改;如果别的CPU内核要读主存这块数据,该缓存行必须回写到主存,状态变为共享。

core的写操作只能在缓存行状态是已修改独占时可以自由执行(如果没有独占,同一份数据存在于多个core的缓存中,不能直接修改)。

如果在缓存行是共享状态,core要先发送一条"我要独占"的请求给总线,总线会通知其他所有core(向其他所有的core广播一个请求),要求其他core先把它们对应缓存中的缓存行标识为失效状态(如果它们有的话);

这个广播动作叫做RFO(Request For Ownership),即获取当前对应缓存行数据的所有权

只有获得独占权后,core才能开始修改数据,并且此时core知道,这个缓存行只有一份拷贝,就在自己的缓存中,core写完以后,将状态置为已修改

上面说过,core会一直嗅探总线,监听读写请求。

对于已修改状态的缓存行,如果别的core要读主存这块数据,该缓存行必须插入总线、回写到主存,状态变为共享


8.2.3. Invalid(失效)缓存行

这种状态的缓存行会被忽略; 一旦缓存行被标识为’失效’,那对于CPU来说,等同于它从来没被加载到缓存中。


8.2.4. Shared(共享)缓存行

和主存数据保持一致的拷贝,这种状态下的缓存行可以在任意时刻抛弃。

上面说过,core会一直嗅探总线,监听读写请求。

对于共享状态的缓存行,如果其他core有独占广播,并且命中,则会将该缓存行状态置为失效状态


9. 扩展

查阅资料时,发现以下内容,适合继续深入了解、学习。


10. Reference