美文网首页
指令重排序和内存屏障

指令重排序和内存屏障

作者: salix_ | 来源:发表于2020-04-14 09:44 被阅读0次

    一:指令重排序

    指令乱序有两种情况,一种是编译器做的优化,另外一种就是cpu流水线操作指令的延迟性。指令重排序是指编译器的对指令执行顺序的优化。但是也可能造成问题。(比如单例模式DCL指令重排带来的并发问题)

    1.cpu乱序

    cpu的乱序不是cpu主动在调整指令的顺序,cpu也是顺序取址执行的。
    int a=n*m; int b=q+p;比如这两条指令,他们没有任何关系,所以乱序是无所谓的,假设q,p都在cache中,命中数据快,a的运算用乘法处理器,b是加法处理器。不冲突,所以,第二条指令就有可能先进行运算。这就是所谓的“顺序流入,乱序流出”。

    2.编译器调整指令顺序

    编译器的让指令的乱序才是真正对指令顺序做了调整。
    乱序同样是为了优化
    a++; int b=a+1; c++;
    上面三条指令,第2条跟第1条有关,第3条跟第1条第2条都无关系。第1条执行的时候,第2条来了就得阻塞。那不如让他们分隔开,执行顺序变成132。例子可能有些不恰当,主要是理解指令无关就有可能为了优化而重排。

    3.乱序的问题(从网上找了个例子)

    排序是可以优化,然而有些程序逻辑,单纯从上下文是看不出它们的因果关系的。比如:
    addr=5; val=data;
    从表面上看,addr和data是没有什么联系的,完全可以放心的去乱序执行。但是如果这是在某某设备驱动程序中,这两个变量却可能对应到设备的地址端口和数据端口。并且,这个设备规定了,当你需要读写设备上的某个寄存器时,先将寄存器编号设置到地址端口,然后就可以通过对数据端口的读写而操作到对应的寄存器。那么这么一来,对前面那两条指令的乱序执行就可能造成错误。
    对于这样的逻辑,我们姑且将其称作隐式的因果关系;而指令与指令之间直接的输入输出依赖,也姑且称作显式的因果关系。CPU或者编译器的乱序是以保持显式的因果关系不变为前提的,但是它们都无法识别隐式的因果关系。

    二:内存屏障

    前面收了指令重排序,那我为了优化任意排序可行嘛?肯定是不可以的,怎样避免这种问题呢?就是内存屏障,简单来说就是,a,b两段代码,在ab之间加一个屏障,a、b内部可能会重排序,但是a、b的代码时绝对不会混到一起的。这就是屏障。

    1.内存屏障介绍

    硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
    内存屏障有两个作用:
    阻止屏障两侧的指令重排序;
    强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
    对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
    对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

    2. java的volatile、final

    java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。

    LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
    StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
    LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
    StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

    volatile语义中的内存屏障

    volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:
    在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
    在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;
    这样避免了volatile变量和其它指令重排序、线程之间实现了通信。

    final语义中的内存屏障

    多线程写final变量也是安全的。这也是内存屏障的起的作用:
    写final域:在编译器写final域完毕,构造体结束之前,会插入一个StoreStore屏障,保证前面的对final写入对其他线程/CPU可见,并阻止重排序。

    相关文章

      网友评论

          本文标题:指令重排序和内存屏障

          本文链接:https://www.haomeiwen.com/subject/donamhtx.html