算法优化:缓存一致性协议MESI

Posted 算法杂话

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法优化:缓存一致性协议MESI相关的知识,希望对你有一定的参考价值。


  • 1. 高速缓存写

  • 2. 缓存一致性

    • 2.1 MESI协议

    • 2.2 内存模型

    • 2.3 MESI状态图


1. 高速缓存写

如果写一个已经缓存的字w(写命中,wirte hit)。在高速缓存中保存它的副本后,怎么更新w在存储层次中下一级存储中的副本?

  • 直写(Write Through)

直写 立即将w的高速缓存块写回到下一级存储中。虽然简单,但是直写的缺点是每次写都会引起总线流量。

  • 回写(Write Back)

回写 在数据更新时只写入高速缓存。只在数据被替换出缓存时,被修改的缓存数据才会被写到下一级存储中。由于局部性,回写可减少总线流量,但它的缺点是增加了复杂性。高速缓存必须为每个高速缓存段维护一个额外的脏位(dirty bit),记录这个高速缓存块是否被修改过。

另一个问题是如何处理写不命中。一种方法是写分配(write-allocate),从下一级存储中取块到高速缓存中,然后更新这个高速缓存。写分配利用了空间局部性,但是缺点是每次写不命中都会导致一个块从下一级存储中传输到高速缓存中。另一种方法是非写分配(non-write-allocate),直接把这个字写到下一级存储中。直写高速缓存通常是非写分配的,回写高速缓存通常是写分配的。

  1. 回写+写分配

a. 读请求时,若命中,直接返回数据;若未命中,在缓存中分配一个缓存块,如果该缓存块是脏的,将脏数据写回到下一级存储中,否则直接从下一级存储中读到缓存中,dirty位修改为clean,返回数据。

b. 写请求时,若命中,直接将新数据写入缓存,并且标记dirty位;若未命中,在缓存中分配一个缓存块,如果该缓存块是脏的,将脏数据写回到下一级存储中,否则直接从下一级存储中读到缓存中,并将新数据写入到该缓存块,并且标记dirty位。

  1. 直写+写不分配
算法优化(十一):缓存一致性协议MESI

解释与“回写+写分配”类似。

2. 缓存一致性

在多核CPU中,每个核都拥有各自的缓存,不同核的缓存可能保存了相同数据的副本,当不同核对各自的缓存进行修改时,可能导致数据的不一致。如果多核CPU共享一个缓存,各个核通过竞争的方式使用缓存,每个时钟周期只允许一个核对缓存进行读写操作,这样可以保证缓存是一致的,但是这种方式会导致CPU计算资源的极大浪费,同时效率极低。

缓存一致性有多种,大多数计算机设备使用的是“窥探(snooping)”协议。窥探的思想是:CPU的各个缓存是互相独立的,但是内存却是共享的,所有缓存的数据都要通过总线写到同一个内存中,因此CPU各个核都能“看见”总线,即各个缓存不仅在进行内存数据交换的时候访问总线,还时刻“窥探”总线,监控其他缓存的行为。因此,当一个缓存往内存中写数据时,其他缓存都能“窥探”到,从而按照一致性协议使各个缓存保持同步。

2.1 MESI协议

MESI协议是四种缓存状态的首字母缩写,任何多核系统中的缓存段都处于这四种状态之一。

  • 失效(Invalid): 要么它已经不在缓存中,要么它的内容已经过时。这种状态的缓存段会被忽略。一旦缓存段被标记为失效,就等同于它从来没有被加载到缓存中。

  • 独占(Exclusive):和S状态一样,它也是和内存一致的拷贝。区别在于,如果一个缓存段处于独占状态,那么它在其他处理器的缓存中的拷贝就会失效,即具有排他性。

  • 已修改(Modified):属于脏段,它们的内容已经被所属的处理器修改了。如果一个缓存段处于已修改状态,那么它在其他处理器的缓存中的拷贝就会失效。已修改缓存段如果被驱逐出缓存或标记为失效,需要把它的内容回写到内存中。

如果把以上这些状态和单核系统中回写模式的缓存进行对比,你会发现I、S和M状态已经有对应的概念:失效/未载入、干净以及脏的缓存段。所以这里的新知识只有E状态,代表独占式访问。这个状态解决了“在我们开始修改某块内存之前,我们需要告诉其它处理器”这一问题:只有当缓存段处于E或M状态时,处理器才能去写它,也就是说只有这两种状态下,处理器是独占这个缓存段的。当处理器想写某个缓存段时,如果它没有独占权,它必须先发送一条“我要独占权”的请求总线,这会通知其它处理器,把它们拥有的同一缓存段的拷贝失效。只有在获得独占权后,处理器才能开始修改数据——这个处理器知道,这个缓存段只有一份拷贝,在我自己的缓存里,所以不会有任何冲突。

反之,如果有其它处理器想读取这个缓存段,独占或已修改的缓存段需要先回到“共享”状态。如果是已修改的缓存段,那么还要先把内容回写到内存中。

MESI定律:所有M状态下的缓存段被回写后,任意缓存级别中的缓存段的数据都与内存保持一致。另外,如果某个缓存段处于E状态,那么在其它的缓存中就不会存在该缓存段。

2.2 内存模型

MESI协议保证了缓存的强一致性,在原理上提供了完整的顺序一致性。MESI协议实现的内存模型,缓存是绝对一致的。不同体系架构提供不同的内存模型,ARM体系架构的机器拥有相对较弱的内存模型:允许读写指令的重排序,这种重排序可能会改变程序在多核环境下的语义。相反,x86则拥有较强的内存模型。

MESI协议的完全一致性隐含的基本假设:

  1. 缓存一收到总线事件,就可以在当前指令周期内迅速做出响应;
  2. 处理器如实地按照程序的顺序,把内存操作指令送到缓存,并且前一条指令执行完成后才能发送下一条。

只要满足以上两点,缓存一致性就能得到绝对的保证。但是由于效率的原因,CPU通常都无法保证以上两点:

  • 缓存不会及时响应总线事件。如果总线发来一条信息,要使某个缓存失效,但是如果此时缓存正在处理其他事情(比如和CPU传输数据),这个消息可能无法在当前的时钟周期得到处理,而是进入所谓的“失效队列(Invalidation Queue)”,直到缓存有空。对于到来的失效请求,失效确认信号Ack马上发出,不需要等待。

  • 处理器通常不会严格按照程序向缓存发送内存操作指令(比如有乱序执行功能的处理器)。顺序执行的处理器有时候也无法保证内存操作的顺序,例如需要的数据不在缓存中,CPU就不能为了载入缓存而一直等待。

  • 写操作首先发起获得独占权的请求(Read for Ownership,RFO),CPU如果严格按照程序的顺序执行内存操作指令,即修改数据之前,必须要等到所有其他缓存的失效确认(Invalidation Acknowledge),等待的过程严重影响CPU的效率,因此现代CPU大都采用存储缓冲(Store Buffer)来暂时缓存写入的数据,等所有的失效确认完成后,再往内存中回写数据。

由于引入了存储缓冲和失效队列,CPU的指令执行顺序就变得混乱,读操作有可能会读取到过时的数据(失效操作还在失效队列中),写操作完成的时间可能要比程序中的时间要晚(写操作的数据还在存储缓冲区)。内存模型分弱内存模型和强内存模型。弱内存模型的体系架构中,指令的重排序和各种缓冲的步骤都是被允许的,如果你需要确保某种结果,需要自己插入合适的内存屏障——它能防止重排序,并且等待队列中的操作全部完成。而在强内存模型的体系架构中则相反,CPU负责实现复杂的操作来保证一致性,用户代码简单但是执行效率低。

那么弱内存模型的体系架构中,用户代码如何保证指令重排序、存储缓冲以及失效队列不会引起缓存不一致的问题呢?答案是使用内存屏障(memory barriers)。

写屏障(store barrier):在执行屏障后面的指令之前,先执行所有已经在存储缓冲区中的指令。

读屏障(load barrier):在执行任何的加载指令之前,先执行所有已经在失效队列的指令。

有了内存屏障,就可以保证缓存的一致性了。

2.3 MESI状态图

算法优化(十一):缓存一致性协议MESI

example1.

算法优化(十一):缓存一致性协议MESI

exmaple2.

算法优化(十一):缓存一致性协议MESI

example3.

算法优化(十一):缓存一致性协议MESI

example4.

算法优化(十一):缓存一致性协议MESI

example5.

算法优化(十一):缓存一致性协议MESI

example6.

example7.


以上是关于算法优化:缓存一致性协议MESI的主要内容,如果未能解决你的问题,请参考以下文章

Java 并发MESI缓存一致性协议

MESI缓存一致性协议

MESI协议-缓存一致性协议

MESI协议-缓存一致性协议

MESI协议-缓存一致性协议

并发编程MESI--CPU缓存一致性协议