关于cpu:CPU-缓存一致性协议-MESI

30次阅读

共计 5118 个字符,预计需要花费 13 分钟才能阅读完成。

CPU 高速缓存(Cache Memory)

CPU 为何要有高速缓存

CPU 在摩尔定律的领导下以每 18 个月翻一番的速度在倒退,然而内存和硬盘的倒退速度远远不迭 CPU。这就造成了高性能能的内存和硬盘价格及其低廉。然而 CPU 的高度运算须要高速的数据。为了解决这个问题,CPU 厂商在 CPU 中内置了大量的高速缓存以解决 I\O 速度和 CPU 运算速度之间的不匹配问题。

在 CPU 拜访存储设备时,无论是存取数据抑或存取指令,都趋于汇集在一片间断的区域中,这就被称为局部性原理。

工夫局部性(Temporal Locality):如果一个信息项正在被拜访,那么在近期它很可能还会被再次拜访。

比方循环、递归、办法的重复调用等。

空间局部性(Spatial Locality):如果一个存储器的地位被援用,那么未来他左近的地位也会被援用。

比方程序执行的代码、间断创立的两个对象、数组等。

带有高速缓存的 CPU 执行计算的流程

  1. 程序以及数据被加载到主内存
  2. 指令和数据被加载到 CPU 的高速缓存
  3. CPU 执行指令,把后果写到高速缓存
  4. 高速缓存中的数据写回主内存

目前风行的多级缓存构造

因为 CPU 的运算速度超过了 1 级缓存的数据 I\O 能力,CPU 厂商又引入了多级的缓存构造。

多级缓存构造

多核 CPU 多级缓存一致性协定 MESI

多核 CPU 的状况下有多个一级缓存,如何保障缓存外部数据的统一, 不让零碎数据凌乱。这里就引出了一个一致性的协定 MESI。

MESI 协定缓存状态

MESI 是指 4 中状态的首字母。每个 Cache line 有 4 个状态,可用 2 个 bit 示意,它们别离是:

缓存行(Cache line): 缓存存储数据的单元。

状态 形容 监听工作
M 批改 (Modified) 该 Cache line 无效,数据被批改了,和内存中的数据不统一,数据只存在于本 Cache 中。 缓存行必须时刻监听所有试图读该缓存行绝对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成 S(共享)状态之前被提早执行。
E 独享、互斥 (Exclusive) 该 Cache line 无效,数据和内存中的数据统一,数据只存在于本 Cache 中。 缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行须要变成 S(共享)状态。
S 共享 (Shared) 该 Cache line 无效,数据和内存中的数据统一,数据存在于很多 Cache 中。 缓存行也必须监听其它缓存使该缓存行有效或者独享该缓存行的申请,并将该缓存行变成有效(Invalid)。
I 有效 (Invalid) 该 Cache line 有效。

留神:
对于 M 和 E 状态而言总是准确的,他们在和该缓存行的真正状态是统一的,而 S 状态可能是非统一的 。如果一个缓存将处于 S 状态的缓存行作废了,而另一个缓存实际上可能曾经独享了该缓存行,然而该缓存却不会将该缓存行升迁为 E 状态,这是因为其它缓存不会播送他们作废掉该缓存行的告诉,同样因为缓存并没有保留该缓存行的 copy 的数量,因而(即便有这种告诉)也没有方法确定本人是否曾经独享了该缓存行。

从下面的意义看来 E 状态是一种投机性的优化:如果一个 CPU 想批改一个处于 S 状态的缓存行,总线事务须要将所有该缓存行的 copy 变成 invalid 状态,而批改 E 状态的缓存不须要应用总线事务。

### MESI 状态转换

了解该图的前置阐明:
1. 触发事件

触发事件 形容
本地读取(Local read) 本地 cache 读取本地 cache 数据
本地写入(Local write) 本地 cache 写入本地 cache 数据
远端读取(Remote read) 其余 cache 读取本地 cache 数据
远端写入(Remote write) 其余 cache 写入本地 cache 数据

2.cache 分类:
前提:所有的 cache 独特缓存了主内存中的某一条数据。

本地 cache: 指以后 cpu 的 cache。
触发 cache: 触发读写事件的 cache。
其余 cache: 指既除了以上两种之外的 cache。
留神:本地的事件触发 本地 cache 和触发 cache 为雷同。

上图的切换解释:

状态 触发本地读取 触发本地写入 触发远端读取 触发远端写入
M 状态(批改) 本地 cache:M
触发 cache:M
其余 cache:I
本地 cache:M
触发 cache:M
其余 cache:I
本地 cache:M→E→S
触发 cache:I→S
其余 cache:I→S
同步主内存后批改为 E 独享, 同步触发、其余 cache 后本地、触发、其余 cache 批改为 S 共享
本地 cache:M→E→S→I
触发 cache:I→S→E→M
其余 cache:I→S→I
同步和读取一样, 同步实现后触发 cache 改为 M,本地、其余 cache 改为 I
E 状态(独享) 本地 cache:E
触发 cache:E
其余 cache:I
本地 cache:E→M
触发 cache:E→M
其余 cache:I
本地 cache 变更为 M, 其余 cache 状态该当是 I(有效)
本地 cache:E→S
触发 cache:I→S
其余 cache:I→S
当其余 cache 要读取该数据时,其余、触发、本地 cache 都被设置为 S(共享)
本地 cache:E→S→I
触发 cache:I→S→E→M
其余 cache:I→S→I
当触发 cache 批改本地 cache 独享数据时时,将本地、触发、其余 cache 批改为 S 共享. 而后触发 cache 批改为独享,其余、本地 cache 批改为 I(有效),触发 cache 再批改为 M
S 状态 (共享) 本地 cache:S
触发 cache:S
其余 cache:S
本地 cache:S→E→M
触发 cache:S→E→M
其余 cache:S→I
当本地 cache 批改时,将本地 cache 批改为 E, 其余 cache 批改为 I, 而后再将本地 cache 为 M 状态
本地 cache:S
触发 cache:S
其余 cache:S
本地 cache:S→I
触发 cache:S→E→M
其余 cache:S→I
当触发 cache 要批改本地共享数据时,触发 cache 批改为 E(独享), 本地、其余 cache 批改为 I(有效), 触发 cache 再次批改为 M(批改)
I 状态(有效) 本地 cache:I→S 或者 I→E
触发 cache:I→S 或者 I →E
其余 cache:E、M、I→S、I
本地、触发 cache 将从 I 有效批改为 S 共享或者 E 独享,其余 cache 将从 E、M、I 变为 S 或者 I
本地 cache:I→S→E→M
触发 cache:I→S→E→M
其余 cache:M、E、S→S→I
既然是本 cache 是 I,其余 cache 操作与它无关 既然是本 cache 是 I,其余 cache 操作与它无关

下图示意了,当一个 cache line 的调整的状态的时候,另外一个 cache line 须要调整的状态。

MESI
M×××
E×××
S××
I

举个栗子来说:

假如 cache 1 中有一个变量 x = 0 的 cache line 处于 S 状态 (共享)。
那么其余领有 x 变量的 cache 2、cache 3 等 x 的 cache line 调整为 S 状态(共享)或者调整为 I 状态(有效)。

### 多核缓存协同操作

假如有三个 CPU A、B、C,对应三个缓存别离是 cache a、b、c。在主内存中定义了 x 的援用值为 0。

#### 单核读取

那么执行流程是:
CPU A 收回了一条指令,从主内存中读取 x。
从主内存通过 bus 读取到缓存中(远端读取 Remote read), 这是该 Cache line 批改为 E 状态(独享).

#### 双核读取

那么执行流程是:
CPU A 收回了一条指令,从主内存中读取 x。
CPU A 从主内存通过 bus 读取到 cache a 中并将该 cache line 设置为 E 状态。
CPU B 收回了一条指令,从主内存中读取 x。
CPU B 试图从主内存中读取 x 时,CPU A 检测到了地址抵触。这时 CPU A 对相干数据做出响应。此时 x 存储于 cache a 和 cache b 中,x 在 chche a 和 cache b 中都被设置为 S 状态 (共享)。

#### 批改数据

那么执行流程是:
CPU A 计算实现后发指令须要批改 x.
CPU A 将 x 设置为 M 状态(批改)并告诉缓存了 x 的 CPU B, CPU B 将本地 cache b 中的 x 设置为 I 状态 (有效)
CPU A 对 x 进行赋值。

#### 同步数据

那么执行流程是:

CPU B 收回了要读取 x 的指令。
CPU B 告诉 CPU A,CPU A 将批改后的数据同步到主内存时 cache a 批改为 E(独享)
CPU A 同步 CPU B 的 x, 将 cache a 和同步后 cache b 中的 x 设置为 S 状态(共享)。

MESI 优化和他们引入的问题
—————

缓存的一致性消息传递是要工夫的,这就使其切换时会产生提早。当一个缓存被切换状态时其余缓存收到音讯实现各自的切换并且收回回应音讯这么一长串的工夫中 CPU 都会期待所有缓存响应实现。可能呈现的阻塞都会导致各种各样的性能问题和稳定性问题。

### CPU 切换状态阻塞解决 – 存储缓存(Store Bufferes)

比方你须要批改本地缓存中的一条信息,那么你必须将 I(有效)状态告诉到其余领有该缓存数据的 CPU 缓存中,并且期待确认。期待确认的过程会阻塞处理器,这会升高处理器的性能。应为这个期待远远比一个指令的执行工夫长的多。

#### Store Bufferes

为了防止这种 CPU 运算能力的节约,Store Bufferes 被引入应用。处理器把它想要写入到主存的值写到缓存,而后持续去解决其余事件。当所有生效确认(Invalidate Acknowledge)都接管到时,数据才会最终被提交。
这么做有两个危险

#### Store Bufferes 的危险

第一、就是处理器会尝试从存储缓存(Store buffer)中读取值,但它还没有进行提交。这个的解决方案称为 Store Forwarding,它使得加载的时候,如果存储缓存中存在,则进行返回。
第二、保留什么时候会实现,这个并没有任何保障。

`
value = 3;

void exeToCPUA(){
value = 10;
isFinsh = true;
}
void exeToCPUB(){
if(isFinsh){
//value 肯定等于 10?!
assert value == 10;
}
}

`

试想一下开始执行时,CPU A 保留着 finished 在 E(独享) 状态,而 value 并没有保留在它的缓存中。(例如,Invalid)。在这种状况下,value 会比 finished 更迟地摈弃存储缓存。齐全有可能 CPU B 读取 finished 的值为 true,而 value 的值不等于 10。

即 isFinsh 的赋值在 value 赋值之前。

这种在可辨认的行为中产生的变动称为重排序(reordings)。留神,这不意味着你的指令的地位被歹意(或者好心)地更改。

它只是意味着其余的 CPU 会读到跟程序中写入的程序不一样的后果。

~ 顺便提一下 NIO 的设计和 Store Bufferes 的设计是十分相像的。~

### 硬件内存模型

执行生效也不是一个简略的操作,它须要处理器去解决。另外,存储缓存(Store Buffers)并不是无穷大的,所以处理器有时须要期待生效确认的返回。这两个操作都会使得性能大幅升高。为了应酬这种状况,引入了生效队列。它们的约定如下:

* 对于所有的收到的 Invalidate 申请,Invalidate Acknowlege 音讯必须立即发送
* Invalidate 并不真正执行,而是被放在一个非凡的队列中,在不便的时候才会去执行。
* 处理器不会发送任何音讯给所解决的缓存条目,直到它解决 Invalidate。

即使是这样处理器未然不晓得什么时候优化是容许的,而什么时候并不容许。
罗唆处理器将这个工作丢给了写代码的人。这就是内存屏障(Memory Barriers)。

> 写屏障 Store Memory Barrier(a.k.a. ST, SMB, smp_wmb) 是一条通知处理器在执行这之后的指令之前,利用所有曾经在存储缓存(store buffer)中的保留的指令。

> 读屏障 Load Memory Barrier (a.k.a. LD, RMB, smp_rmb) 是一条通知处理器在执行任何的加载前,先利用所有曾经在生效队列中的生效操作的指令。

`
void executedOnCpu0() {
value = 10;
// 在更新数据之前必须将所有存储缓存(store buffer)中的指令执行结束。
storeMemoryBarrier();
finished = true;
}
void executedOnCpu1() {
while(!finished);
// 在读取之前将所有生效队列中对于该数据的指令执行结束。
loadMemoryBarrier();
assert value == 10;
}

`

##### 援用文章
http://www.importnew.com/1058…
https://www.cnblogs.com/yanlo…


正文完
 0