缓存一致性协议(MESI)
# 缓存一致性协议(MESI)
# 一、前言
现代计算机CPU都不是直接操作内存,而是直接操作寄存器和高速缓存,如何保证缓存的一致性,现在常用的解决办法有两种:
总线锁
总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存,会导致CPU执行效率降低,现在很少被使用
缓存锁
MESI, 当一个CPU要修改缓存中的变量时,会对缓存加锁,同时会通过总线通知别的CPU,让他们的变量副本失效,这样同样可以保证一次只有一个CPU修改变量的值,从而保证缓存一致性
# 二、MESI协议缓存状态
MESI是四个单词的首字母缩写,Modified修改,Exclusive独占,Shared共享,Invalid无效
M 修改 (Modified)
表示当前CPU的高速缓存中的变量副本是独占的
E 独享、互斥 (Exclusive)
表示当前CPU的高速缓存中的变量副本是独占的
S 共享 (Shared)
处于S状态表示CPU中的变量副本和主存中数据一致,而且多个CPU都可以处于S状态
I 无效 (Invalid)
表示当前CPU的高速缓存的变量副本处于不合法状态,不可以直接使用,需要从主内存重新读取,flag的初始状态就是I
# 三、MESI协议工作流程
假设有三个CPU A、B、C,对应三个缓存分别是cache a、b、 c。在主内存中定义了x的引用值为0。执行流程如下:
1、CPU A发出了一条指令,从主内存中读取x。
从主内存通过bus读取到缓存中(远端读取Remote read),这是该Cache line修改为E状态(独享)
2、CPU B发出了一条指令,从主内存中读取x。
CPU B试图从主内存中读取x时,CPU A检测到了地址冲突。这时CPU A对相关数据做出响应。此时x 存储于cache a和cache b中,x在chche a和cache b中都被设置为S状态(共享)。 3、CPU A 计算完成后发指令需要修改x.
CPU A 将x设置为M状态(修改)并通知缓存了x的CPU B, CPU B将本地cache b中的x设置为I状态(无效)
CPU A 对x进行赋值。 4、CPU B 发出了要读取x的指令。
CPU B 通知CPU A,CPU A将修改后的数据同步到主内存时cache a 修改为E(独享)
CPU A同步CPU B的x,将cache a和同步后cache b中的x设置为S状态(共享)。
# 四、其它
# 1、什么是缓存行(cacheline)?
缓存存储数据的单元。缓存行大小通常为64Bytes
# 2、缓存行伪共享
什么是伪共享?
CPU缓存系统中是以缓存行(cache line)为单位存储的。目前主流的CPU Cache 的 Cache Line 大小都是64Bytes。在多线程情况下,如果需要修改“共享同一个缓存行的变量”,就会无意中影响彼此的性能,这就是伪共享(False Sharing)
如何解决?
Java8中新增了一个注解:@sun.misc.Contended
。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended
才会生效。
@sun.misc.Contended
public final static class TulingVolatileLong {
public volatile long value = 0L;
//public long p1, p2, p3, p4, p5, p6;
}
2
3
4
5
# 3、MESI优化和他们引入的问题
存在问题
缓存的一致性消息传递是要时间的,这就使其切换时会产生延迟。当一个缓存被切换状态时其他缓存收到消息完成各自的切换并且发出回应消息这么一长串的时间中CPU都会等待所有缓存响应完成。可能出现的阻塞都会导致各种各样的性能问题和稳定性问题
解决方法
CPU切换状态阻塞解决-存储缓存(Store Bufferes
)。为了避免这种CPU运算能力的浪费,Store Bufferes
被引入使用。处理器把它想要写入到主存的值写到缓存,然后继续去处理其他事情。当所有失效确认(Invalidate Acknowledge)都接收到时,数据才会最终被提交。