Cache一致性协议与伪共享问题

发布时间:2022-06-21 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Cache一致性协议与伪共享问题脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

Cache一致性协议

在说伪共享问题之前,有必要聊一聊什么是Cache一致性协议

局部性原理

时间局部性:如果一个信息项正在被访问,那么在近期它很可能还会被再次访问 比如循环、方法的反复调用等

空间局部性:如果一个存储器的位置被引用,那么将来他附近的位置也会被引用 比如顺序结构、数组

Cache的作用

CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。为了解决这个问题,CPU厂商在CPU中内置了少量的高速缓存Cache,以解决访存速度和CPU运算速度之间不匹配的问题

带Cache的CPU访存过程

CPU和Cache交换数据以为单位。Cache与主存以为单位,一个缓存行(Cache Line)对应一个主存块

  • Cache命中,则直接从Cache中读取数据
  • Cache不命中,则访问主存,并将一个主存块调入Cache中,存入为一个缓存行。这个过程中可能由于Cache满而发生替换,换算包括RAND、FIFO、LRU、LFU

  • Cache命中时
    • 写回法(wrITe back):CPU只将数据写入Cache,只有当数据调出Cache时,才写入主存
    • 写穿法(write through):CPU同时将数据写入Cache和主存
  • Cache不命中时
    • 写分配法:从主存中将数据块调入Cache,并修改Cache,和写回法配合使用
    • 非写分配法:只写入主存,不调入Cache,和写穿法配合使用

Cache和主存的映射方式(三种):直接映射、全相联映射、组相联映射

Cache一致性协议与伪共享问题

如果是单CPU结构,这么执行没有其他问题。但是现代系统往往包含多个CPU,每个CPU都有各自的Cache。多核CPU的情况下有多个一级缓存,如何保证缓存内部数据的一致性,不让系统数据混乱。这里就引出了Cache一致性协议——MESI。注意,这并不是唯一的缓存一致性协议,还有其他协议如MOSei(相对于MESI多引入了一个Owned状态,并重新定义了S状态),这里不多介绍

MESI协议详解

MESI(Modified Exclusive Shared Or Invalid),也称伊利诺斯协议,是一种广泛使用的、支持写回策略的缓存一致性协议。MESI协议其实就是使用4种状态来标记各个缓存行(Cache Line)的状态,而这些状态英文首字母缩写就构成了“MESI”

MESI协议中的各种状态

每个缓存行都使用一个状态来标记,该状态总共有4种,使用2bit进行存储:

  • M(Modified):相应的数据只被缓存在该CPU的Cache中,但数据是被修改过的(脏数据),即与主存中的数据不一致。该缓存行中的内存需要在未来的某个时间点,但必须是其它CPU读取主存中相应内存之前,将数据写回主存
  • E(Exclusive):相应的数据只被缓存在该CPU的缓存中,数据是未被修改过的,与主存中的数据一致
  • S(Shared):相应的数据被多个CPU缓存,且各个CPU的Cache中的数据和主存都是一致的
  • I(Invalid):该缓存行中的数据是无效的,因为有其他CPU修改了数据

总线嗅探机制(监听)

每个CPU都可以感知其他CPU的行为,比如读、写某个缓存行,这就是嗅探机制,也称监听。所有的缓存行(除了Invalid状态)都需要监听自己和其他CPU对相应的缓存行的读写操作,也称触发事件,从而根据触发事件和自身状态,进行状态的转换

各种触发事件

@H_301_126@
触发事件 描述
本地读取(Local Read) 本CPU读取本Cache的数据
本地写入(Local Write) 本CPU向本Cache写入数据
远端读取(Remote Read) 其他CPU读取它们各自Cache的数据
远端写入(Remote Write) 其他CPU向它们各自Cache写入数据

MESI中各个状态之间的转换

下图描述了当前缓存行在不同触发事件下的状态切换:

Cache一致性协议与伪共享问题

下表是对上图的一个详细解释:

Cache一致性协议与伪共享问题

举例

假设CPU0、CPU1、CPU2、CPU3中有一个缓存行(包含变量x)都是S状态

此时CPU1要对变量x进行写操作,这时候通过总线嗅探机制,CPU0、CPU2、CPU3中的缓存行会置为I状态(无效),然后给CPU1发响应,收到全部响应后CPU1会完成对变量x的写操作,并更新CPU1内的缓存行为M状态,但不会将数据x同步到主存中

接着CPU0想要对变量x执行读操作,却发现本地缓存行是I状态,就会触发CPU1去把缓存行写回到主存中,然后CPU0再去主存中同步最新的值

其他一些细节

1、写缓冲 前面的描述隐藏了一些细节,比如实际CPU1在执行写操作,更新缓存行的时候,其实并不会等待其他CPU的状态都置为I状态,才去做些操作,这是一个同步行为,效率很低。当前的CPU都引入了写缓存器技,也就是在CPU和cache之间又加了一层buffer,在CPU执行写操作时直接向写缓冲写入数据,然后就忙其他事去了,等其他CPU都置为I之后,CPU1才把buffer中的数据写入到缓存行中

2、多级缓存

Cache一致性协议与伪共享问题

现代系统都会采用多级缓存架构,L1-L3级缓存,其中L3缓存是所有CPU共享的一个缓存,但MESI的描述中并没有涉及L3缓存。其实上文提到的所有跟“主存”交换数据的地方,在L3缓存存在的情况下,都应该替换为L3缓存。比如我上一节举的例子中,CPU0中某缓存行是I,CPU1 中是M。当CPU0想到执行local read操作时,就会触发CPU1中的缓存写入到主存中,然后CPU0从主存中取最新的缓存行。其实这里的描述是不准确的,因为由于L3缓存的存在,这里其实是直接从L3缓存读取缓存行,而不直接访问主存。个人认为是如果在描述MESI的状态流转时,如果引入L3缓存,会使得描述过于复杂,因此一般的描述都会刻意忽略L3缓存

作者:酒冽        出处:https://www.cnblogs.COM/frankiedyz/p/15786362.htML 版权:本文版权归作者和博客园共有 转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

伪共享

由于内存和Cache之间的交换单位是内存块/缓存行,因此如果访问一个变量,会将一整个内存块读入一个缓存行。但是如果多个线程访问的变量不相同,且这些变量在内存中的位置临近,那么很可能在同一个缓存行中。在Cache一致性协议(如MESI协议)的约束下,多个线程(CPU)在并行读写相对应的缓存行会有限制,因此其他线程不得不去访问低级别的Cache甚至是主存,这会导致cache没有起到真正的作用,程序性能下降

伪共享示例

Cache一致性协议与伪共享问题

如图,线程1访问变量x,而线程2访问变量y,而这两个变量在内存中的位置临近(在同一个内存块中),虽然线程都将该内存块读入到各自的工作内存(Cache)中,但是在Cache一致性协议的约束下,同一时间两个线程很难自由地读写相同位置的缓存行,那么可能就会让其中一个线程去低级别的内存中读写数据,性能因此降低,这就是伪共享

一般地址连续的多个变量更可能被放在同一个缓存行中,例如创建数组时,数组中的多个元素更可能被放入同一个缓存行中

如何避免伪共享问题

JDK8之前

JDK8之前,使用字节填充的方式,即创建一个变量时,使用填充字段填充该变量所在的缓存行,从而避免多个变量被放入同一个缓存行中,如下:

public final static class FilledLong {
    public volatile long value = 0L;
    public long p1, p2, P3, p4, p5, p6;
}

一般来说,缓存行为64 Byte,而经过填充的FilledLong对象有7*8 Byte=56 Byte,而FilledLong对象的对象头也有8 Byte,正好填满一个缓存行

JDK8及之后

JDK8提供了一个注解——sun.misc.Contended,用于解决伪共享问题,上述代码可以修改为如下:

@sun.misc.Contended
public final static class FilledLong {
	public volatile long value = 0L;
}

Thread类中,也有这样的字段,如下:

@sun.misc.Contended("tlr")
long threadLocalRandomSeed;

/** PRobe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;

/** Secondary seed isolated From public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;

但是,@Contended注解只用于Java核心类,而用户类路径下的类使用该注解,需要添加JVM参数-XX:-RestrictContended。填充度默认为128 Byte,也可以自定义宽度,通过JVM参数-XX:ContendedPaddingWidth来设定

脚本宝典总结

以上是脚本宝典为你收集整理的Cache一致性协议与伪共享问题全部内容,希望文章能够帮你解决Cache一致性协议与伪共享问题所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。