在有多个核的处理器的处理器中,每一个核都有本身的cache,而如何确保多个核的cache内容的一致则是一个很容易遇到的问题,MESI协议就是一个专门用来解决cache一致性的协议。不少处理器使用的都是MESI协议或者MESI协议的变体,而MESI协议其实也是MSI协议的变种。MESI协议采用了回写(write-back)的策略来更新cache,使得其性能进一步提升,但也带来了额外的风险,回写带来的问题能够在编写程序时使用内存屏障来规避。java
MESI协议名字的由来是由其描述的四个cache状态组成的,分别是M(modified)、E(exclusive)、S(shared)和I(invalid)。各个状态的描述具体以下:缓存
状态 | 描述 |
---|---|
Modified | 当前cache的内容有效,数据已被修改并且与内存中的数据不一致,数据只在当前cache里存在 |
Exclusive | 当前cache的内容有效,数据与内存中的数据一致,数据只在当前cache里存在 |
Shared | 当前cache的内容有效,数据与内存中的数据一致,数据在多个cache里存在 |
Invalid | 当前cache无效 |
MESI协议实际上是一个状态机,cache的状态会跟根据外部事件的刺激而发生转移,具体的事件分为两类:处理器对cache的请求和总线对cache的请求:并发
而状态之间的转换以下图:性能
初始状态 | 操做 | 响应 |
---|---|---|
Invalid(I) | PrRd |
|
PrWr |
|
|
Exclusive(E) | PrRd |
|
PrWr |
|
|
Shared(S) | PrRd |
|
PrWr |
|
|
Modified(M) | PrRd |
|
PrWr |
|
初始状态 | 操做 | 响应 |
---|---|---|
Invalid(I) | BusRd |
|
BusRdX/BusUpgr |
|
|
Exclusive(E) | BusRd |
|
BusRdX |
|
|
Shared(S) | BusRd |
|
BusRdX |
|
|
Modified(M) | BusRd |
|
BusRdX |
|
MESI的设计比较简单直接,可是其中有两个地方会致使性能降低:一是更新invalidate状态的cache时,须要尝试从其余cpu甚至是内存获取最新的数据;二是使一个cache变为invalidate时须要等待其余cpu的确认;这两个操做都是比较耗时的,若是cpu在这两个过程当中一直等待的话,就会造成浪费。优化
为了下降写入invalidate状态的cache的延时,能够引入store buffer。既然写入操做不管如何必定会发生,那么cpu就先发出信号通知其余cpu这个cache已经失效,而后再将本次的写操做更新到store buffer中,等到其余cpu都确认收到信号后再将结果写到内存中。spa
这样就避免了更新cache时阻塞等待其余cpu确认的耗时,可是也会致使cpu的更新并无及时写入cache,因此当cpu须要读取cache时,它须要先确认store buffer中是否有所需的数据,这个机制成为store forwarding。值得注意的是,当cpu在读写本身的store buffer时,对应的数据变动其余cpu是感知不到的。设计
当cpu收到使某个cache失效的消息时,预期的行为是cpu立刻执行这个失效操做。但实际上cpu并不会立刻执行失效操做,而是先发送确认收到的消息,而后将失效操做加入到invalidate queue中,queue中的操做随后会在适当的时刻执行(并不必定是立刻)。之因此须要invalidate queue一样是由于invalidate操做开销比较大,cpu为了执行invalidate操做必须丢弃cache,致使cache命中率降低。这样的好处是可以提升cpu的性能,但同时也致使cache中可能存在过时的数据。3d
针对store buffer和invalidate queue这两个优化带来的问题,咱们又提供了内存屏障做为解决方案。内存屏障交给了编写程序的人的手里,利用它就能够规避上面提到的问题。cdn
内存屏障分为写屏障和读屏障,编写程序时能够在指望的地方加入内存屏障。写屏障会强制cpu清空store buffer的内容,也就是将全部的变动都写入cache,随后变动也就写入了内存,使其对其余cpu可见;读屏障会强制cpu执行invalidate queue中的全部invalidate操做,使自身的cache内容失效,从而使cpu从内存或者其余cpu中获取最新的cache数据。blog
MESI协议乍一看和java里的内存模型以及volatile关键字有些类似,后续再详细展开了。