【问题标题】:How is a StoreStore barrier mapped to instructions under x86?StoreStore屏障如何映射到x86下的指令?
【发布时间】:2016-05-20 11:46:05
【问题描述】:

JSR133 cookbook 说:

StoreStore Barriers 序列:Store1;商店商店; Store2 确保 Store1 的数据对其他处理器可见(即刷新到 内存)之前与 Store2 关联的数据和所有后续 存储说明。一般来说,需要 StoreStore 屏障 不保证刷新的严格顺序的处理器 从写缓冲区和/或缓存到其他处理器或主内存。

而且,从说明书中我们知道,对于同步块,Java 编译器会插入一些障碍来防止可能的重新排序:

MonitorEnter
[LoadLoad] <==inserted barrier
[LoadStore]<==inserted barrier

...
[LoadStore]<==inserted barrier
[StoreStore]<==inserted barrier
MonitorExit

但是,由于 x86 不允许重新排序 Read-Read、Read-Write 和 Write-Write。所以上面所有的barrier都会映射到no-ops。也就是说对于x86处理器来说MonitorEnter和MonitorExit之间不会插入barrier。 我的困惑是,如果我们将 StoreStore 映射到 x86 下的 no-op,那么如何保证可见性? 更详细地说,x86 确实使用了存储缓冲区,因此为了使在关键部分执行的写入对其他处理器可见,我们需要刷新存储缓冲区,因此需要写入屏障。从可见性的角度来看,a 应该映射到 sfence/mfence/Lock#?但是食谱说,从防止重新排序的角度来看,它应该映射到无操作。 或者,关键是可见性保证是由 MonitorEnter 完成的,并且 MonitorExit 本身?如果是这样的话,我想他们可能会使用所谓的 Read barrier 和 Write barrier 来保证可见性,对吧?

【问题讨论】:

  • x86 自己的内存模型已经保证所有写入都具有 StoreStore 语义,因此您不必显式提供屏障。这很常见(在 AArch64 上,CAS 实现方面不需要显式障碍,使用的指令提供了您需要的所有保证),尽管 x86 通常提供现代 IS As 的最强保证,因此您根本不需要显式障碍。
  • 谢谢@Voo,也许我的问题可以概括为:在x86下,如果我们将StoreStore映射到no-op(因为x86不允许Write-Write的重新排序)以防止重新排序,那么为了保证可见性,我们可能会将 StoreStore 映射到 sfence/mfence/LOCK 前缀指令。这似乎很奇怪,对吧?
  • x86 上的存储为您提供所需的可见性保证。关于刷新存储缓冲区:确实是必要的(好吧或具有相同效果的类似内容 - 实现细节和所有内容),但这就是 StoreLoad 屏障所做的 - 在 x86 上需要这些。正是出于这个原因,它们也是最昂贵的。

标签: java multithreading


【解决方案1】:

虽然我们可以说内存屏障能够:

  1. 保证可见性(通过刷新存储缓冲区和/或应用 使队列无效)
  2. 防止重新排序(禁止在加载之间重新排序 和/或存储在内存屏障之前或之后)

但这并不意味着内存屏障应该总是同时做以上两件事,Sun JDK 提供的 Unsafe.doPutOrderedXX 方法就是这样一个例子:

SomeClass temp = new SomeClass(); //S1
unsafe.putOrderedObject(this, valueOffset, null);
Object target = temp; //S2

unsafe.putOrderedObject 这里用作 StoreStore 屏障,因此可以防止 重新排序 S1 和 S2,但不保证 S1 的结果对其他处理器/线程可见(因为没有这样的需要)。

有关不安全和易失性的更多信息:

  1. Where is sun.misc.Unsafe documented?

  2. http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

  3. http://jpbempel.blogspot.com/2013/05/volatile-and-memory-barriers.html

也就是说,在x86下,为了防止重排序的enter code here,一个StoreStore可以映射到no-op,为了保证可见性,一个StoreStore应该映射到一些指令比如sfence/mfence/锁定#。

另一个例子是final关键字。为了强制执行 final 的语义(观察到的变量的值不能更改),编译器应该在写入 final 字段和从该构造函数返回之间插入一个 StoreStore 屏障。原因这样做是:确保在将构造对象的引用写入引用变量之前,写入 final 字段应该对其他处理器可见。实际上,这意味着对 ordering 的要求而不是可见性,因此在返回之前写入 final 的结果被刷新(刷新存储缓冲区/或缓存)是 必要的从那个构造函数。因此,在 x86 下,JVM 不会为 final 字段插入任何屏障。

【讨论】:

  • 在 x86 上,每条存储指令之间都有一个隐式的 StoreStore 屏障。存储缓冲区总是尽可能快地自行刷新。如果您想让当前核心 wait 直到更早的存储全局可见,然后再执行任何后续加载,即 StoreLoad 屏障,您只需要 mfence 的 locked 指令作为屏障。如果您需要一个商店在在任何以后读取之前可见,您需要一个 StoreLoad 屏障,而不仅仅是 StoreStore。在任何普通 ISA 上都是如此,而不仅仅是 x86:即使您不等待,商店也会很快成为全球可见的。
  • 换句话说具有一致缓存的系统不需要显式刷新,所有可以运行 Java 的普通系统(x86、ARM、MIPS、PowerPC 等) ) 是缓存一致的。屏障只会让当前核心等待直到一个存储可见,之后的存储或以后的存储和加载才被允许变得可见。
  • 顺便说一句,unsafe.putOrderedObject 不仅仅是一个 StoreStore 障碍,它是一个商店。 robsjava.blogspot.com/2013/06/a-faster-volatile.html 展示了如何将其用作发布存储。 (或者可能不发布,如果它真的只是 storestore 而不是 loadstore)。但无论如何,它实际上在某处写入,因此您应该将它与指向您实际要写入的类成员变量的valueOffset 一起使用。
  • 另外,final 字段生成 LoadStore,而不是 Java 中的 StoreStore 屏障 groups.google.com/forum/…
猜你喜欢
  • 2018-10-23
  • 1970-01-01
  • 1970-01-01
  • 2020-01-30
  • 2015-04-24
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多