理解内存障碍

Cap*_*ngs 4 java x86 volatile memory-barriers

我试图以一种对Java无锁程序员有用的级别来理解内存障碍,我觉得这个级别介于学习volatile和根据x86手册学习存储/加载缓冲区之间。

我花了一些时间阅读一堆博客/食谱,并提出了以下摘要。知识渊博的人可以看一下摘要,看看我是否错过或列出了不正确的内容。

围栏:

Name             : LFENCE/Load Barrier/Acquire Fence
Barriers         : LoadLoad + LoadStore
Details          : Given sequence {Load1, LFENCE, Load2, Store1}, the
                   barrier ensures that Load1 can't be moved south and
                   Load2 and Store1 can't be moved north of the
                   barrier. 
                   Note that Load2 and Store1 can still be reordered.

Buffer Effect    : Causes the contents of the LoadBuffer 
                   (pending loads) to be processed for that CPU.This
                   makes program state exposed from other CPUs visible
                   to this CPU before Load2 and Store1 are executed.

Cost on x86      : Either very cheap or a no-op.
Java instructions: Reading a volatile variable, Unsafe.loadFence()
Run Code Online (Sandbox Code Playgroud)

SFENCE

Name             : SFENCE/Store Barrier/Release Fence
Barriers         : StoreStore + LoadStore
Details          : Given sequence {Load1, Store1, SFENCE, Store2,Load2}
                   the barrier ensures that Load1 and Store1 can't be
                   moved south and Store2 can't be moved north of the 
                   barrier.
                   Note that Load1 and Store1 can still be reordered AND 
                   Load2 can be moved north of the barrier.
Buffer Effect    : Causes the contents of the StoreBuffer flushed to 
                   cache for the CPU on which it is issued.
                   This will make program state visible to other CPUs
                   before Store2 and Load1 are executed.
Cost on x86      : Either very cheap or a no-op.
Java instructions: lazySet(), Unsafe.storeFence(), Unsafe.putOrdered*()
Run Code Online (Sandbox Code Playgroud)

MFENCE

Name             : MFENCE/Full Barrier/Fence
Barriers         : StoreLoad
Details          : Obtains the effects of the other three barrier.
                   Given sequence {Load1, Store1, MFENCE, Store2,Load2}, 
                   the barrier ensures that Load1 and Store1 can't be
                   moved south and Store2 and Load2 can't be moved north
                   of the barrier.
                   Note that Load1 and Store1 can still be reordered AND
                   Store2 and Load2 can still be reordered.
 Buffer Effect   : Causes the contents of the LoadBuffer (pending loads) 
                   to be processed for that CPU.
                   AND
                   Causes the contents of the StoreBuffer flushed to
                   cache for the CPU on which it is issued.
 Cost on x86     : The most expensive kind.
Java instructions: Writing to a volatile, Unsafe.fullFence(), Locks
Run Code Online (Sandbox Code Playgroud)

最后,如果SFENCE和MFENCE都耗尽了storeBuffer(使高速缓存行无效,并等待来自其他CPU的确认),为什么一个无操作而另一个非常昂贵呢?

谢谢

(从Google的“机械同情”论坛中交叉发布)

Pet*_*des 6

您正在使用Java,因此真正重要的是Java内存模型。编译时(包括JIT)优化将在Java内存模型的限制范围内重新排序您的内存访问,而不是JVM恰好针对JIT编译的更强大的x86内存模型。(请参阅我对内存如何重新排序如何帮助处理器和编译器的回答

尽管如此,对x86的了解仍可以为您提供具体的基础知识,但不要陷入认为x86上的Java就像x86上的汇编一样工作的陷阱。(或者说整个世界都是x86。许多其他体系结构的排序都很弱,例如Java内存模型。)


就内存排序而言,x86 LFENCE和x SFENCE是no-op,除非您使用了movnt弱排序的高速缓存绕过存储。正常负载是隐式获取负载,而正常存储是隐式释放存储


您的表中有一个错误:根据英特尔的指令集参考手册,SFENCE“未按照装载指令进行排序”。它只是一个StoreStore障碍,不是LoadStore障碍。

(该链接是英特尔pdf的html转换。有关官方版本的链接,请参见标签wiki。)

lfence 是一个LoadLoad和LoadStore障碍,因此您的表是正确的。

但是CPU并不能真正“提前”缓冲负载。他们执行这些操作,并在结果可用后立即开始将结果用于乱序执行。(通常,即使在L1高速缓存命中时,使用加载结果的指令也已在加载结果准备好之前进行了解码和发布)。这是装载和存储之间的根本区别。


SFENCE之所以便宜,是因为它实际上不必耗尽存储缓冲区。这是实现它的一种方法,它以性能为代价使硬件保持简单。

MFENCE之所以昂贵是因为它是阻止StoreLoad重新排序的唯一障碍。请参阅《法案》中的Jeff Preshing的“ 内存重新排序”以获取解释,以及一个测试程序,该程序实际上演示了在实际硬件上的StoreLoad重新排序。

Jeff Preshing的博客文章对于理解无锁编程和内存排序语义是金牌。我通常将他的博客链接在我对内存排序问题的解答中。如果您有兴趣阅读更多我所写的内容(主要是C ++ / asm,而不是Java),则可以在此上使用搜索来找到那些答案。


有趣的事实:在x86上进行的任何原子性的读-修改-写操作也是一个完整的内存障碍。lock隐含在上的前缀xchg [mem], reg也是一个完整的障碍。 lock add [esp], 0是内存障碍的一个常见习语,否则在没有发布之前mfence是无操作的。(堆栈存储器在L1中几乎总是很热,并且不共享)。

因此,在x86上,增加原子计数器的性能与您要求的内存排序语义无关。(例如,c ++ 11 memory_order_relaxedmemory_order_seq_cst(顺序一致性))。但是,请使用适当的任何内存顺序语义,因为其他体系结构可以执行原子操作而没有完整的内存障碍。在不需要时强制编译器/ JVM使用内存屏障是一种浪费。