理解内存屏障的含义

8
我希望能够为Java无锁程序员翻译有关内存屏障的相关内容。我认为,这个层次介于仅学习volatile和从x86手册学习Store/Load缓冲区的工作之间。
我花了一些时间阅读了许多博客/食谱,并得出了以下总结。请有更多知识的人查看摘要,以查看我是否遗漏或列出了一些错误。
LFENCE:
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()

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*()

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

最后,如果SFENCE和MFENCE都会清空存储缓冲区(使缓存行失效并等待其他CPU的确认),为什么一个操作是无操作,而另一个操作非常昂贵?
谢谢。
(从谷歌的机械同情论坛转帖)
1个回答

8
您正在使用Java,因此真正重要的是Java内存模型。编译时(包括JIT),优化将重新排列内存访问,但仅限于Java内存模型的限制,而不是JVM刚好为之JIT编译的更强的x86内存模型。(请参阅我的回答内存重排序如何帮助处理器和编译器?
尽管如此,了解x86可以给您的理解打下一个具体的基础,但不要陷入这样的思维误区,即Java在x86上的工作方式类似于x86汇编语言。(或者整个世界都是x86。许多其他架构是弱有序的,就像Java内存模型一样。)
x86中的LFENCESFENCE在内存排序方面是无操作的,除非你使用了弱序缓存绕过存储movnt。正常加载隐式地获取加载,并且正常存储隐式地释放存储
您的表格中存在错误: SFENCE is "not ordered with respect to load instructions", 根据英特尔指令集参考手册。它只是一个StoreStore屏障,而不是LoadStore屏障。
(该链接是英特尔pdf文件的html转换。请参阅标签wiki以获取官方版本的链接。) lfence是一个LoadLoad和LoadStore屏障,因此您的表格是正确的。
但是,CPU实际上并没有预先缓存加载。他们会立即执行并开始使用结果进行乱序执行,即使在L1高速缓存命中的情况下,通常使用加载结果的指令已经被解码和发出,而加载结果还没有准备好。这是加载和存储之间的根本区别。

SFENCE很便宜,因为它实际上不需要清空存储缓冲区。这是一种保持硬件简单的实现方式,但代价是性能。

MFENCE很昂贵,因为它是唯一阻止StoreLoad重排序的屏障。请参见Jeff Preshing的Memory Reordering Caught in the Act以获取解释和一个真正演示了在实际硬件上发生StoreLoad重排序的测试程序。

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


有趣的事实:在x86上进行的任何原子读取-修改-写入操作也是完整的内存屏障。在xchg [mem],reg上隐含了一个lock前缀,这也是一个完整的屏障。在mfence可用之前,lock add [esp],0是一个常见的习惯用语,用于进行内存屏障,否则是无操作的。(堆栈内存几乎总是在L1中热度较高,并且不共享)。
因此,在x86上,增加原子计数器的性能与您请求的内存排序语义无关。(例如,c++11 memory_order_relaxed vs. memory_order_seq_cst(顺序一致性))。但是,请使用适当的内存排序语义,因为其他架构可以执行原子操作而无需完整的内存屏障。在您不需要时强制编译器/JVM使用内存屏障是一种浪费。

嗨Peter,非常感谢你提供如此详细的答案。我现在明白了,作为Java程序员,我只需要关注编译器根据JMM进行重新排序。而屏障是一种防止这些重新排序的方法。 - CaptainHastings
@CaptainHastings:是的,這基本上是真的,唯獨知道硬體中哪些是便宜的可以幫助您編寫能夠更好地優化的代碼。或者理解您閱讀的其他東西。請記住,即使x86 ISA允許StoreLoad重新排序(參見Preshing的“Memory Reordering Caught in the Act”博客),所以如果您使用包括StoreLoad的完整屏障,JVM需要運行內存屏障指令。 x86上的所有其他障礙都僅阻止編譯器重新排序。因此,不完全正確的是只關心編譯器重排序,但確實是使用弱有序的JMM編程。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接