MESI 协议

背景

现代处理器为了提高访问数据的效率,在每个CPU核心上都会有多级容量小,速度快的缓存(L1 cache,L2 cache,多核心共享L3 cache等),用于缓存常用的数据。由于从内存取数据要比从缓存取数据慢近100倍,数据被修改时也只是先更新cache,并不是直接写回到主存中。由此造成了缓存中的数据与内存不一致。如果系统是单核处理器,所有线程看到的都是缓存中的最新数据,当然没有问题。但如果系统是多核处理器,同一份主存数据可能会被缓存到多个核心 cache中,在运行中只要其中一个核心修改了缓存中的值,如果其他CPU核心没有得到及时的通知,就会造成缓存不一致的情况,影响系统的运行结果。MESI协议的出现,就是为了解决多核处理器时代,缓存不一致的问题的。(Intel 是MESI,其他厂商还有MOSI、MSI、Dragon Firefly等)

概念

MESI协议
NUMA架构CPU
由图可知,每个Socket代表一个CPU插槽,一个CPU包含多个核心core,每个核心有自己私有的L1,L2级缓存,同时每个核心共享L3级缓存,多个CPU共享主存。其中,缓存中的最小缓存单元称之为缓存行(cache-line),在X86架构下,cache-line长度为64字节。MESI就代表者cache-line的四种缓存状态。MESI协议基于内存屏障、锁总线(无法锁缓存行时)机制实现。

当CPU要读取数据时,只要缓存的状态不是I都可以从缓存中读,否则就要从主存中读取。这一读操作可能会被某个处于M或E状态的CPU截获,该CPU将修改的数据写出到内存,并将自己设为S状态后这一读操作才继续进行。只有缓存状态是E或M时,CPU才可以修改其中的数据,修改后缓存即处于M状态。如果CPU要修改数据时发现其缓存不处于E或M状态,则需要发出特殊的RFO指令(Read For Ownership),将其它CPU的缓存设为I状态。

M: Modified(被修改的),处于这一状态的数据只在本CPU中有缓存,且其数据已被修改,没有更新到内存中;
E: Exclusive(独占的),处于这一状态的数据只在本CPU中有缓存,且其数据没有被修改,与内存一致;
S: Shared(共享的),处于这一状态的数据在多个CPU中有缓存;
I: Invalid(无效的),本CPU中的这份缓存已经无效了。

MESI状态转换图
在这里插入图片描述

转换过程说明:

第一:某个CPU(CPU A)发起本地写请求(Local Write),比如对某个内存地址的变量赋值,如果此时如果所有CPU的Cache中都没加载此内存地址,即此内存地址对应的Cache Line为无效状态(Invalid),则CPU A中的Cache Line保存了最新内存变量值以后,其状态被修改为Modified。随后,如果CPU B发起对同一个变量的读操作(Remote Read),则CPU A在总线上嗅探到这个读请求以后,先将Cache Line里修改过的数据回写(Write Back)到Memory中,然后在内存总线上放一份Cache Line的拷贝作为应答,最后再将自身的Cache Line的状态修改为Shared,由此产生的结果是CPU A与CPU B里对应的Cache Line的状态都为Shared。

第二:在第一点的基础上,CPU A发起本地写请求导致自身的Cache Line状态变为Modified以后,如果此时CPU B发起同一个内存地址的写请求(Remote Write),则我们看到状态图里此时CPU A的Cache Line状态为Invalid,其原因是如下:CPU B此时发出的是一个特殊的请求——“读并且打算修改数据”(read with intent to modify),当CPU A从总线上嗅探到这个请求后,会先阻止此请求并取得总线的控制权(Takes control of bus),随后将Cache Line里修改过的数据回写(Write Back)到Memory中,再将此Cache Line的状态修改为Invalid(这是因为其他CPU要改数据,所以没必要改为Shared了)。与此同时,CPU B发现之前的请求并没有得到响应,于是重新再发起一次请求,此时由于所有CPU的Cache里都没有内存副本了,所以CPU B的Cache就从Memory中加载最新的数据到Cache Line中,随后修改数据,然后改变Cache Line的状态为Modified。

缓存行伪共享问题

MESI缓存一致性协议操作的最小单元是缓存行(cache line),缓存行内数据的修改、写入内存、写入其他缓存等操作都会改变其状态,这样,在共享缓存多核架构里,数据结构如果组织不好,就非常容易出现多个核线程反复修改同一条缓存行的数据导致缓存行状态频繁变化,从而导致严重性能问题(总线延迟、总线风暴),这就是伪共享现象。
下图就是一个伪共享的例子,core1上运行的线程想修改变量x,core2上运行的线程想修改变量y,但x和y刚好在一个缓存行上。每个线程都要去竞争缓存行的所有权来更新变量。如果core1获得了所有权,缓存控制器将会使core2中对应的缓存行失效。当core2获得了所有权然后执行更新操作,core1就要使自己对应的缓存行失效。这会来来回回的经过L3缓存,大大影响了性能。如果互相竞争的核心位于不同的插槽,就要额外横跨插槽连接,问题可能更加严重。在这里插入图片描述

缓存行填充:解决缓存行伪共享导致性能问题的解决方案,就是缓存行填充,通过填充缓存行,使得某个核心线程频繁操作的数据独享缓存行,这样就不会出现伪共享问题了。
PS:Disruptor编程模式,缓存行对其, JDK 8 @Contended关键字。

总结

每个CPU核心中都有寄存器,用于临时存储从缓存中加载的数据和cpu运算修改后的数据;
由于速度差异较大,CPU是不直接和主存进行数据通信的,而是借助于各级缓存来完成的;
在CPU核心中引入StoreBuffer(一个存放store存储指令的队列,队列长度为36),用于异步将寄存器中的值刷回缓存cache中,当CPU要写变量的时候,会将写操作封装成一个store指令,放入Store Buffer队列就立即返回了,其他的至于Store Buffer什么时候将store指令生效,CPU是不关心的;同理,Load Buffer是一个存放加载数据指令的队列,长度为64,当CPU要加载某个变量的值时,也是封装成一个load指令,放入LoadBuffer队列后,就立即返回,这些过程CPU都是异步操作的,根据MESI缓存一致性协议,CPU将数据写回到cache中后,就能够保证各CPU缓存数据的一致性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值