当前位置: 首页 > 知识库问答 >
问题:

理解记忆障碍

费承载
2023-03-14

我试图在一个对java无锁程序员有用的层次上理解内存障碍。我觉得这个级别介于学习挥发物和学习x86手册中存储/加载缓冲区的工作之间。

我花了一些时间阅读了大量的博客/食谱,并总结了以下内容。能不能请一些知识更渊博的人看看总结,看看我是否遗漏了什么或列错了什么。

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都耗尽了storeBuffer(使缓存线失效并等待来自其他CPU的ACK),为什么一个是无操作,另一个是非常昂贵的操作?

谢谢

(谷歌机械同情论坛交叉发布)

共有1个答案

齐向笛
2023-03-14

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

尽管如此,学习x86可以给你的理解提供一个具体的基础,但是不要陷入这样的陷阱,认为x86上的Java就像x86上的组装一样。(或者整个世界都是x86。许多其他架构都是弱有序的,比如Java内存模型。)

x86LFENCESFENCE在内存排序方面是无操作的,除非您使用movnt弱有序缓存绕过存储。正常负载是隐式获取负载,正常存储是隐式发布存储。

根据英特尔的指令集参考手册,您的表中有一个错误:SFENCE“未按加载指令排序”。它只是一个商店屏障,而不是LoadStore屏障。

(该链接是英特尔PDF的html转换。有关官方版本的链接,请参阅x86标记wiki。)

lfence是Load和LoadStore的屏障,因此您的表是正确的。

但CPU并没有真正提前“缓冲”加载。他们会这样做,并在结果可用时立即开始使用结果进行无序执行。(通常,在加载结果就绪之前,使用加载结果的指令已经解码并发出,即使在一级缓存命中时也是如此)。这是加载和存储之间的根本区别。

SFENCE很便宜,因为它实际上不需要耗尽存储缓冲区。这是实现它的一种方法,它以牺牲性能为代价,保持了硬件的简单性。

MFENCE很昂贵,因为它是阻止StoreLoad重新排序的唯一障碍。有关解释,请参阅Jeff Presing的Memory Reorder Catt in the Act,以及一个实际演示StoreLoad重新排序的测试程序在真实硬件上。

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

有趣的事实:x86上的任何原子读-修改-写操作也是一个完整的内存屏障。lock前缀隐含在xchg[mem], reg上,也是一个完整的屏障。lock add[esp],0是内存屏障的常见习惯用法,否则是无操作的,在mford可用之前。(堆栈内存在L1中几乎总是很热,并且不共享)。

因此,在x86上,无论您请求的内存排序语义学如何,递增原子计数器都具有相同的性能。(例如c 11memory_order_relaxedvs.memory_order_seq_cst(顺序一致性))。不过,使用任何合适的内存顺序语义学,因为其他架构可以在没有完全内存障碍的情况下执行原子操作。强迫编译器/JVM在不需要时使用内存障碍是一种浪费。

 类似资料:
  • 问题内容: 内存屏障可确保数据缓存保持一致。但是,是否可以保证TLB保持一致? 我看到一个问题,即在线程之间传递MappedByteBuffer时,JVM(java 7更新1)有时会因内存错误(SIGBUS,SIGSEG)而崩溃。 例如 没有Thread.yield(),我有时会在force(),put()和C的memcpy()中崩溃,所有这些都表明我试图非法访问内存。使用Thread.yield

  • 我读过关于记忆障碍如何工作的不同文章。 例如,用户Johan在这个问题中的回答说,内存屏障是CPU执行的指令。 虽然用户Peter Cordes在这个问题中的评论对CPU如何重新排序指令说了以下内容: 它的读取速度比执行速度快,因此它可以看到即将到来的指令窗口。有关详细信息,请参阅 x86 标签 wiki 中的一些链接,如 Agner Fog 的微搜索 pdf,以及大卫·坎特对英特尔哈斯韦尔设计的

  • A我是Drools的新手,读过一些文档和教程,当然,我的问题有一个简单的解决方案。我使用onle规则文件和类计数器,如下所示。环境为:Wintel JDK 1.7(71),DROOLS 6.1.0 有个规矩 这是kModule 跑步的结果 我的问题是: > 为什么“反淋浴1”规则只涉及最后插入的事实?是否存在任何隐藏行为? 3、为什么在count==1的retract object Counter

  • 问题内容: Java应该没有内存泄漏,但是仍然可能。当我的程序出现内存泄漏时,我可以修复它(我希望)。但是,当某些第三方程序包具备该功能时,我该怎么办?几乎什么都没有,除非不使用此软件包。 还有其他解决方案吗?我喜欢沙盒的想法。您被允许在某个区域内做任何您想做的事情,而您的“身体上的”没有能力打扰其他人。有没有办法为Java中的内存使用创建此类沙箱?想象一下=创建用于内存使用的沙箱,允许某些程序包

  • 什么是记忆化搜索?记忆化搜索在本质上,还是动态规划,只是实现方式采用了深度优先搜索的形式,但是它不像深度优先搜索那样重复枚举所有情况,而是把已经计算的子问题保存下来,这样就和动态规划的思想不谋而合了。 本篇文章会通过最简单的例子对记忆化搜索进行深入讲解,帮助大家学会什么是记忆化搜索。 一、记忆化搜索 记忆化搜索是一种搜索的形式,对搜索的结果用数组或其他数据结构记录下来。若当前状态搜索过了,则返回已

  • 问题内容: 我有一个程序可以通过递归传递大量数据,例如1000个变量。递归将至少运行50或60次。我担心的是,由于没有足够的空间,是否有可能数据在内存位置上被覆盖,或者如果没有内存,那么我会得到一些例外,即程序内存已经用完(我没有收到这样的错误)? 是否存在错误的解决方案,因为该程序没有更多的内存并且在现有位置上被覆盖? 问题答案: 涉及两个存储区域:堆栈和堆。堆栈是保存方法调用的当前 状态 (即