CPU多级缓存一致性协议MESI
多核CPU的情况下有多个一级缓存,如何保证缓存内部数据的一致,不让系统数据混乱。这里就引出了一个一致性的协议MESI。
MESI是指四种状态的首字母。每个Cache line(缓存行)有4个状态,可用两个bit表示,分别是:
状态 | 描述 | 监听任务 |
---|---|---|
M修改 | 该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。 | 缓存行必须时刻监听所有试图读取该缓存行相对主内存的操作,这种操作必须在缓存将该缓存行写会主内存并将状态变成S(共享)状态之前被延迟执行 |
E独享、互斥 | 该Cache line有效,数据和内存中的数据一致,数据只存在本Cache中 | 缓存行必须监听其它缓存读主内存中该缓存行的操作,一旦有这种操作,该缓存行需要编程S(共享)状态 |
S共享 | 该Cache line有效,数据和内存中的数据一致,数据存在很多Cache中 | 缓存行必须时刻监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效 |
I 无效 | 该Cache line无效 | 无 |
对于M和E状态而言总是精确的,他们和该缓存行的真正状态是一致的,而S状态可能是非一致的。
缓存行伪共享
CPU缓存系统中是以缓存行为单位存储的。目前主流CPU的缓存行大小为64Bytes。在多线程情况下,如果需要修改“共享同一个缓存行的变量”,就会无意中影响彼此的性能,这就是伪共享。
- @sun.misc.Contended。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在jvm启动时设置 -XX:-RestrictContended 才会生效。
MESI优化和它们引入的问题
- CPU切换状态阻塞解决-存储缓存(Store Buffers)
如果你需要修改本地缓存行中的一条信息,那么你必须将I状态通知到其他拥有该缓存数据的CPU缓存中,并且等待确认。等待确认的过程会阻塞处理器,这样会降低处理器的性能。 - Store Bufferes
为了避免这种CPU运算能力的浪费,Store Bufer被引入使用。处理器把它想写入到主内存的值写到缓存,然后继续去处理其它事情。当所有失效确认都接收到时,数据才会最终被提交。 - Store Bufferes的风险
处理器会尝试从存储缓存中读取值,但它还没有进行提交。这个的解决方案称为Store Forwarding,它使得加载的时候,如果存储缓存中存在,则进行返回。并且保存什么时候会完成,没有任何保证。
硬件内存模型
执行失效也不是一个简单的操作,它需要处理器去处理。另外,存储缓存(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)是一条告诉处理器在执行任何的加载前,先应用所有已经在失效队列中的失效操作的指令。