学习内容链接:https://www.cnblogs.com/yanlong300/p/8986041.html(原文写的非常好,建议看原文。这里仅仅是自己学习后,再梳理一遍增加印象~)
为什么有MESI协议?
CPU的计算能力与日俱增,然而内存及硬盘的发展速度远远落后于CPU,CPU厂商为了解决这个问题在CPU内增加了少量告诉缓存来匹配CPU的计算速度。随着计算机的硬件架构逐渐走向了多缓存(时间局部性、空间局部性)多核心的结构。为了保证计算中缓存内部数据的一致性,提出了一个MESI协议来解决这个问题。
什么是MESI协议?
多核CPU的情况下有多个一级缓存,如何保证缓存内部数据的一致,不让系统数据混乱。这里就引出了一个一致性的协议MESI来解决这个问题。
MESI指的是:
M(Modified 修改)、E(Exclusive 独享)、S(Shared 共享)、 I(无效 Invalid) 四种状态的首字母。
多核缓存协同操作示例
假设有三个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状态(共享)。
缺陷: 一个数据被修改前,将其他CPU的缓存数据修改成无效状态。需要其他CPU进行处理。需要进行数据修改的CPU在等待其他所有CPU回复的时间会很长。会导致各种性能问题及稳定性问题。
更优方法:存储缓存store buffers
存储缓存模型为了解决MESI协议存在的效率问题被引入。在有CPU想修改变量值时,会先将数据写入到存储缓存中,然后切换去处理其他事情。当收集齐了其他CPU的失效标识时,再将修改的数据写入到主内存中。这样提升了CPU的计算时间。
Store buffers的风险
- cpu会尝试从存储缓存中优先读取数据。
- 存储在存储缓存中的数据什么时候进行同步没有保证。
试想一下开始执行时,CPU A保存着finished在E(独享)状态,而value并没有保存在它的缓存中。(例如,Invalid)。在这种情况下,value会比finished更迟地抛弃存储缓存。完全有可能CPU B读取finished的值为true,而value的值不等于10。
即isFinsh的赋值在value赋值之前。
这种在可识别的行为中发生的变化称为重排序(reordings)。注意,这不意味着你的指令的位置被恶意(或者好意)地更改。
它只是意味着其他的CPU会读到跟程序中写入的顺序不一样的结果。
Store buffers风险的解决方案
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)是一条告诉处理器在执行任何的加载前,先应用所有已经在失效队列中的失效操作的指令