CPU缓存一致性( 三 )

提示: MESI 协议在 MSI 的基础上增加了 E(独占)状态,以减少只有一份缓存的写操作造成的总线通信 。
写缓冲区 & 失效队列MESI 协议保证了 Cache 的一致性,但完全地遵循协议会影响性能 。 因此,现代的 CPU 会在增加写缓冲区和失效队列将 MESI 协议的请求异步化,以提高并行度:

  • 写缓冲区(Store Buffer)
由于在写入操作之前,CPU 核心 1 需要先广播 RFO 请求获得独占权,在其它核心回应 ACK 之前,当前核心只能空等待,这对 CPU 资源是一种浪费 。因此,现代 CPU 会采用 “写缓冲区” 机制:写入指令放到写缓冲区后并发送 RFO 请求后,CPU 就可以去执行其它任务,等收到 ACK 后再将写入操作写到 Cache 上 。
  • 失效队列(Invalidation Queue)
由于其他核心在收到 RFO 请求时,需要及时回应 ACK 。但如果核心很忙不能及时回复,就会造成发送 RFO 请求的核心在等待 ACK 。因此,现代 CPU 会采用 “失效队列” 机制:先把其它核心发过来的 RFO 请求放到失效队列,然后直接返回 ACK,等当前核心处理完任务后再去处理失效队列中的失效请求 。
事实上,写缓冲区和失效队列破坏了 Cache 的一致性 。
因为在未同步的情况下,程序可能会有多种执行顺序 。这也是为什么JAVA里还需要volatile关键字,因为引入写缓冲区或失效队列后就变成弱数据一致性,不能满足 强数据一致性: 保证在任意时刻任意副本上的同一份数据都是相同的,或者允许不同,但是每次使用前都要刷新确保数据一致,所以最终还是一致 。
总结
  1. 在 CPU Cache 的三级缓存中,会存在 2 个缓存一致性问题:
纵向 - Cache 与内存的一致性问题: 在修改 Cache 数据后,如何同步回内存?
横向 - 多核心 Cache 的一致性问题: 在一个核心修改 Cache 数据后,如何同步给其他核心 Cache?
  1. Cache 与内存的一致性问题有 2 个策略:
写直达策略: 始终保持 Cache 数据和内存数据一致,在每次写入操作中都会写入内存;
写回策略: 只有在脏 Cache 块被替换出去的时候写回内存,减少写回内存的次数;
  1. 多核心 Cache 一致性问题需要满足 2 点特性:
写传播(总线嗅探): 每个 CPU 核心的写入操作,需要传播到其他 CPU 核心;
事务串行化(总线仲裁): 各个 CPU 核心所有写入操作的顺序,在所有 CPU 核心看起来是一致 。
  1. MESI 协议能够满足以上 2 点特性,通过 “已修改、独占、共享、已失效” 4 个状态实现了 CPU Cache 的一致性;
  2. 现代 CPU 为了提高并行度,会在增加 写缓冲区 & 失效队列 将 MESI 协议的请求异步化, 从内存的视角看就是指令重排,破坏了 CPU Cache 的一致性 。也是为什么使用volatile关键字的原因




推荐阅读