Java 内存模型 - volatile 和 x86
Java memory model - volatile and x86
我正在尝试了解 java volatile 的内在特性及其语义,以及它到底层架构及其指令的转换。如果我们考虑以下博客和资源
fences generated for volatile, What gets generated for read/write of volatile and
这是我收集的:
- 易失性读取在其后插入 loadStore/LoadLoad 个障碍(x86 上的 LFENCE 指令)
- 它可以防止随后的负载重新排序 writes/loads
- 它应该保证加载被其他线程修改的全局状态,即在 LFENCE 之后,其他线程所做的状态修改对当前线程可见 CPU.
我很难理解的是:Java does not emit LFENCE on x86
即读取 volatile 不会导致 LFENCE... 我知道 x86 的内存排序会阻止使用 lods/stored 对负载进行重新排序,因此第二个要点得到了处理。但是,我假设为了让该线程看到状态,应该发出 LFENCE 指令以保证在执行栅栏后的下一条指令之前耗尽所有 LOAD 缓冲区(根据 Intel 手册)。我知道 x86 上有 cahce 一致性协议,但易失性读取仍应耗尽缓冲区中的任何负载,不是吗?
在 x86 上,缓冲区固定到缓存行。如果缓存行丢失,则不会使用缓冲区中的值。因此无需围栏或耗尽缓冲区;它们包含的值必须是当前值,因为另一个核心在不首先使缓存行无效的情况下无法修改数据。
X86提供TSO。因此,在硬件级别上,您可以免费获得以下障碍 [LoadLoad][LoadStore][StoreStore]。唯一缺少的是 [StoreLoad]。
A load 具有 acquire 语义
r1=X
[LoadLoad]
[LoadStore]
商店具有发布语义
[LoadStore]
[StoreStore]
Y=r2
如果你先做一个存储然后加载你最终会得到这个:
[LoadStore]
[StoreStore]
Y=r2
r1=X
[LoadLoad]
[LoadStore]
问题是加载和存储仍然可以重新排序,因此顺序不一致;这对于 Java 内存模型是强制性的。他们防止这种情况的唯一方法是使用 [StoreLoad]。
[LoadStore]
[StoreStore]
Y=r2
[StoreLoad]
r1=X
[LoadLoad]
[LoadStore]
最合乎逻辑的地方是将其添加到写入中,因为通常读取比写入更频繁。所以写会变成:
[LoadStore]
[StoreStore]
Y=r2
[StoreLoad]
由于X86提供了TSO,以下fences可以no-ops:
[LoadLoad][LoadStore][StoreStore]
所以唯一相关的是 [StoreLoad],这可以通过 MFENCE
或 lock addl %(RSP),0
来完成
LFENCE 和 SFENCE 与这种情况无关。 LFENCE 和 SFENCE 用于弱排序的加载和存储(例如 SSE 的那些)。
[StoreLoad] 在 X86 上的作用是停止执行加载,直到存储缓冲区被耗尽。这将确保在存储变得全局可见(已离开存储缓冲区并进入 L1d)之后负载是全局可见的(因此从 memory/cache 读取)。
我正在尝试了解 java volatile 的内在特性及其语义,以及它到底层架构及其指令的转换。如果我们考虑以下博客和资源
fences generated for volatile, What gets generated for read/write of volatile and
这是我收集的:
- 易失性读取在其后插入 loadStore/LoadLoad 个障碍(x86 上的 LFENCE 指令)
- 它可以防止随后的负载重新排序 writes/loads
- 它应该保证加载被其他线程修改的全局状态,即在 LFENCE 之后,其他线程所做的状态修改对当前线程可见 CPU.
我很难理解的是:Java does not emit LFENCE on x86 即读取 volatile 不会导致 LFENCE... 我知道 x86 的内存排序会阻止使用 lods/stored 对负载进行重新排序,因此第二个要点得到了处理。但是,我假设为了让该线程看到状态,应该发出 LFENCE 指令以保证在执行栅栏后的下一条指令之前耗尽所有 LOAD 缓冲区(根据 Intel 手册)。我知道 x86 上有 cahce 一致性协议,但易失性读取仍应耗尽缓冲区中的任何负载,不是吗?
在 x86 上,缓冲区固定到缓存行。如果缓存行丢失,则不会使用缓冲区中的值。因此无需围栏或耗尽缓冲区;它们包含的值必须是当前值,因为另一个核心在不首先使缓存行无效的情况下无法修改数据。
X86提供TSO。因此,在硬件级别上,您可以免费获得以下障碍 [LoadLoad][LoadStore][StoreStore]。唯一缺少的是 [StoreLoad]。
A load 具有 acquire 语义
r1=X
[LoadLoad]
[LoadStore]
商店具有发布语义
[LoadStore]
[StoreStore]
Y=r2
如果你先做一个存储然后加载你最终会得到这个:
[LoadStore]
[StoreStore]
Y=r2
r1=X
[LoadLoad]
[LoadStore]
问题是加载和存储仍然可以重新排序,因此顺序不一致;这对于 Java 内存模型是强制性的。他们防止这种情况的唯一方法是使用 [StoreLoad]。
[LoadStore]
[StoreStore]
Y=r2
[StoreLoad]
r1=X
[LoadLoad]
[LoadStore]
最合乎逻辑的地方是将其添加到写入中,因为通常读取比写入更频繁。所以写会变成:
[LoadStore]
[StoreStore]
Y=r2
[StoreLoad]
由于X86提供了TSO,以下fences可以no-ops:
[LoadLoad][LoadStore][StoreStore]
所以唯一相关的是 [StoreLoad],这可以通过 MFENCE
或 lock addl %(RSP),0
LFENCE 和 SFENCE 与这种情况无关。 LFENCE 和 SFENCE 用于弱排序的加载和存储(例如 SSE 的那些)。
[StoreLoad] 在 X86 上的作用是停止执行加载,直到存储缓冲区被耗尽。这将确保在存储变得全局可见(已离开存储缓冲区并进入 L1d)之后负载是全局可见的(因此从 memory/cache 读取)。