【问题标题】:Cost of volatile writes易失性写入的成本
【发布时间】:2017-04-17 22:34:56
【问题描述】:

我正在研究 x86 硬件中 Java 中易失性写入的成本。 我计划在共享内存位置上使用 Unsafe 的 putLongVolatile 方法。查看实现,putLongVolatile get 被转换为 Link 中的 Unsafe_SetLongVolatile,随后转换为 AtomicWrite,然后是栅栏 Link

简而言之,每个易失性写入都会转换为原子写入,然后是一个完整的围栏(mfence 或 x86 中的锁定添加指令)。

问题:

1) 为什么 x86 需要 fence() ?由于商店-商店排序,一个简单的编译器障碍还不够吗?完整的围栏似乎非常昂贵。

2) 用 unsafe 的 putLong 代替 putLongVolatile 是更好的选择吗?它在多线程情况下是否可以正常工作?

【问题讨论】:

  • 让我给出我的一般优化意见:可能没关系。首要任务是找到应用程序的瓶颈,然后您可以进行更有意义的基准测试和优化。

标签: java multithreading volatile


【解决方案1】:

问题 1 的答案:

没有完整的栅栏,您就没有 JMM 所需的顺序一致性。

所以 X86 提供了 TSO。因此,您可以免费获得以下障碍 [LoadLoad][LoadStore][StoreStore]。唯一缺少的是 [StoreLoad]。

负载具有获取语义

r1=X
[LoadLoad]
[LoadStore]

商店具有发布语义

[LoadStore]
[StoreStore]
Y=r2

如果您先进行存储然后加载,您最终会得到以下结果:

[LoadStore]
[StoreStore]
Y=r2
r1=X
[LoadLoad]
[LoadStore]

问题是加载和存储仍然可以重新排序,因此它不是顺序一致的;这对于 Java 内存模型是强制性的。他们防止这种情况的唯一方法是使用 [StoreLoad]。最合乎逻辑的地方是将其添加到写入中,因为通常读取比写入更频繁。

这可以通过MFENCElock addl %(RSP),0 来完成

问题 2 的答案:

putLong 的问题在于,不仅 CPU 可以重新排序指令,而且编译器也可以更改代码以导致指令重新排序。

示例:如果您要在循环中执行 putLong,编译器可以决定将写入拉出循环,并且该值将不会对其他线程可见。如果你想要一个低开销的单写性能计数器,你可能想看看 putLongRelease/putLongOrdered(oldname)。这将阻止编译器执行上述技巧。以及免费获得的 X86 上的发布语义。

但是很难对第二个问题给出一个万能的解决方案,因为这取决于你的目标是什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-23
    • 2016-04-04
    • 1970-01-01
    • 1970-01-01
    • 2012-02-08
    • 1970-01-01
    • 2017-08-02
    • 2011-06-17
    相关资源
    最近更新 更多