CPU缓存

想要了解 CPU MESI缓存一致性协议 首先需要了解什么是CPU缓存。说CPU缓存之前,先了解一下CPU执行过程,但是CPU执行指令的过程非常复杂,这里我就直接简化为从内存中加载指令、执行指令、获取内存数据、将执行结果回写回内存。

CPU执行指令的速度是非常快,但是在获取内存数据的话会有很长时间的延时(下图可知在120ns左右),这对于执行指令来说是完全不能接受的,所以就出现了CPU缓存这个东西,CPU缓存是直接集成在CPU中,离CPU最近,所以CPU在获取数据的时候不会直接去内存中获取,而是先从CPU缓存中获取,不需要通过地址总线(就是主板上的一个通道)去内存中去获取,从而提升了程序执行效率,采取了就近的原则。其实根本原因就是CPU运算速度与内存读写速度不匹配导致,这也和我们业务中的Redis类似,读取数据库非常慢,但是读Redis非常快,所以获取数据先从Redis中获取,如果没有再去数据库中读,以加快程序响应速度。其实也给我们一思维就是要是觉得程序执行慢,加缓存就完了(但是加缓冲也会产生各种各样的问题)。

白话MESI缓存一致性协议

L1缓存/L2缓存/L3缓存

上面我们已经知道了CPU缓存这个东西,但是上面图中的L1缓存/L2缓存/L3缓存又是什么,非常简单L1缓存/L2缓存/L3缓存其实统称叫做CPU缓存,只不过分了三级,L1最靠近CPU核心,L2其次,L3再次,运行速度方面:L1最快、L2次快、L3最慢;容量大小方面:L1最小、L2较大、L3最大。CPU会先在最快的L1中寻找需要的数据,找不到再去找次快的L2,还找不到再去找L3,L3都没有那就只能去内存找了。
白话MESI缓存一致性协议
看到这个图片之后我第一想法就是我们在分布式的系统看到的图片真是太像了,所以我想在这样一个类似分布式的框架中我们能从中的到什么有价值的东西,就需要我和大家思考了。

cache line

怎么说着说着就突然就提到了这个不搭噶的名词cache line,这就要从CPU缓存的结构开始说起,从网上找了一个理解起来比较清晰的图片。白话MESI缓存一致性协议
CPU中的高速缓存内部结构是一个拉链散列表,和HashMap的底层结构以及原理十分相似。它分为若干桶,每个桶是一个链表,包含若干缓存条目,每个缓存条目就是一个cache line。一个cache line一般是64 bytes,假设程序中读取某一个int变量,CPU并不是只从主存中读取4个字节,而是会一次性读取64个字节,然后放到cpu cache中。因为往往紧挨着的数据,更有可能在接下来会被使用到。比如遍历一个数组,因为数组空间是连续的,所以并不是每次取数组中的元素都要从主存中去拿,第一次从主存把数据放到cache line中,后续访问的数据很有可能已经在cache中了。(有点类似IO的时候,在进行磁盘读取的时候会把相邻的数据也读取到,也是类似的考虑吧)

再来看一下这句话是不是就很好理解了呢:

在CPU访问存储设备时,无论是存取数据抑或存取指令,都趋于聚集在一片连续的区域中,这就被称为局部性原理。
时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。比如循环、递归、方法的反复调用等。
空间局部性(Spatial Locality):如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。比如顺序执行的代码、连续创建的两个对象、数组等。

缓存条目(cache line)近一步可以分为三个部分:

白话MESI缓存一致性协议
CPU访问内存时,会通过内存地址解码的三个数据:index(桶编号)、tag(缓存条目的相对编号)、offset(变量在缓存条目中的位置偏移)来获取高速缓存中对应的数据。

这时候可以看到Flag这个标识了,然后就可以引出了状态值的概念,然后就可以引出我们所说的MSEI协议了。

MSEI缓存一致性协议

MSEI其实就是四种状态首字母的简写,就代表上面缓存条目(cache line)的四种状态,分别是:

状态 描述
M 修改 (Modified) 该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。
E 独享、互斥 (Exclusive) 该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。
S 共享 (Shared) 该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中。
I 无效 (Invalid) 该Cache line无效。

我觉得文字还是表达的有点苍白直接上图吧:

E状态示例如下:

白话MESI缓存一致性协议
只有Core 0访问变量x,它的Cache line状态为E(Exclusive)。

S状态示例如下:
白话MESI缓存一致性协议
3个Core都访问变量x,它们对应的Cache line为S(Shared)状态。

M状态和I状态示例如下:
白话MESI缓存一致性协议
Core 0修改了x的值之后,这个Cache line变成了M(Modified)状态,其他Core对应的Cache line变成了I(Invalid)状态。

在MESI协议中,每个Cache的Cache控制器不仅知道自己的读写操作,而且也监听(snoop)其它Cache的读写操作。每个Cache line所处的状态根据本核和其它核的读写操作在4个状态间进行迁移。
白话MESI缓存一致性协议
其实我是不想贴这张图的,因为看起来实在是太恶心了,我觉得文字描述起来会简单一点。

既然知道了cache line有四种状态,那么必然会有某个操作会去触发状态的改变,这个某个操作就是:

  1. Local Read表示本内核读本Cache中的值
  2. Local Write表示本内核写本Cache中的值
  3. Remote Read表示其它内核读其它内核Cache中的值
  4. Remote Write表示其它内核写其它内核Cache中的值

一共有四种状态,四种状态下还对应四种操作,那么就有十六种情况,不要慌,虽然说有十六种但是有的状态下的操作是没有意义的,所以会少很多,而且很容易理解,耐心看一定会恍然大悟的。

现在cache line是I(Invalid)状态,看上面的图可知,此时有其他核在修改S(shared)状态下的共享变量,修改的那个核是M(modified)状态,其他核里面的变量状态就是I(Invalid)状态,当前CPU中是脏数据,不可用,其他CPU可能有数据、也可能没有数据;

操作 描述
Local Read 如果其他Cache没有这份数据,本Cache从内存中取数据,cache line状态变为E
Local Read 如果其他核的Cache有这份数据,且状态为M,此核等待其他核将数据刷新到内存后,此核再从内存中读取数据刷新到此核的缓存中,两个核的cache line状态都变为S
Local Read 如果其他核的Cache有这份数据,且状态为S或者E,本Cache从内存中取数据,这些cache line状态变为S
Local Write 因为此核的数据是无效的所以需要从内存中取数据,然后修改自己cache line中的数据,再将状态设置为M(因为发生了修改)
Local Write 如果其他Cache有这份数据,且状态为M,此核等待其他核将数据刷新到内存,并将其他核的cache line状态设置为I后,此核再从内存中读取数据刷新到此核的缓存中,修改自己核中的cache line为M
Local Write 如果其他Cache有这份数据,且状态为S或者E,本核Cache直接修改自己的cache line数据并将状态设置为M,告诉其他核的Cache将他们的cache line设置为I(从而保证自己有效但是其他核是无效的)
Remote Write 我自己都已经是Invalid状态了,别的核怎么操作都与我不搭噶
Remote Read 我自己都已经是Invalid状态了,别的核怎么操作都与我不搭噶

现在cache line是E(Exclusive)状态,看上面的图可知,只有此核的cache line含有内存的副本变量

操作 描述
Local Read 只是自己核读取自己核的数据,状态不变
Local Write 只是自己核修改了自己核的数据,但是状态要修改为M
Remote Write 说明其他核修改了同样的数据,那么为了保证数据不能够被脏读,就需要让这个核的数据无效,需要读的话重新去内存中或其他核中去读,所以这个核的cache line设置为I
Remote Read 说明其他核也读取了同样的数据,那么这个核就是共享了数据,状态设置为S

现在cache line是S(Shared)状态,看上面的图可知,多个核存在此cache line的内存副本

操作 描述
Local Read 这个核读取这个核自己的数据,与其他核不搭噶,所以状态不变
Local Write 这个核修改这个核的数据,首先需要将自己的状态设置为M,同时需要告诉其他核他们的数据已经是过期的数据了,需要设置为I状态
Remote Write 其他核修改了其他核的数据,可以换位思考一下,其实就是其他核发生了其他核的Local Write,当然相对于其他核,这个核就是其他核,所以这个核需要设置为I状态,说明自己的数据已经是过期的数据了
Remote Read 其他核读取其他核的数据,与自己不搭噶,所以状态不变

现在cache line是M(Modified)状态,看上面的图可知,当前核正在修改数据,这个核的数据是最新的,所有的数据都以当前核的数据为准

操作 描述
Local Read 这个核读取这个核自己的数据,与其他核不搭噶,这个核已经是最新的数据了,所以状态不变
Local Write 这个核修改这个核自己的数据,与其他核不搭噶,这个核已经是最新的数据了,修改后数据还是最新的,所以状态不变
Remote Write 其他核需要修改其他核中同样的数据,但是其他核中不是最新的,所以需要从内存中读取,为了保证一致性,这个核首先会将自己最新的数据写到内存中,其他核就和当前这个核拥有了同样的最新数据,然后会将状态设置为I,因为其他核中的数据现在是最新的了,现在自己核的数据是过期的数据
Remote Read 其他核需要读取其他核中同样的数据,但是其他核中不是最新的,所以需要从内存中读取,为了保证一致性,这个核首先会将自己最新的数据写到内存中,其他核就和当前这个核拥有了同样的最新数据,然后会将状态设置为S,因为多个核共享了一个数据

总结

MESI协议为了保证多个CPU cache中共享数据的一致性,定义了cache line的四种状态,而CPU对cache的4种操作可能会产生不一致状态,因此cache控制器监听到本地操作和远程操作的时候,需要对地址一致的cache line状态做出一定的修改,从而保证数据在多个cache之间流转的一致性。

总感觉这种设计方式在实际的应用场景下作用不太大,感觉一个cache line需要不停的去监听其他cache line的状态,而且还需去等其他的状态改变完了才进行下面的操作。但是这个通过状态和监听的方式保证一致性还是值得学习的。

相关文章: