我研究了在x86硬件中使用java语言进行易失性写操作的成本。我计划在一个共享内存位置上使用Unsafe的putLongVolatile方法。查看其实现,putLongVolatile get在Link中被转换为Unsafe_SetLongVolatile,随后被转换为AtomicWrite,后跟围栏Link
简而言之,每个易失性写操作都会被转换为原子写操作,然后是一个完整的栅栏(x86中的mfence或锁定的add指令)。
问题:
1)为什么x86需要围栏()?由于存储-存储排序,一个简单的编译器屏障还不够吗?一道完整的栅栏看起来非常昂贵。
2) putLong是不是更好的替代不安全的putLongVolatile?它在多线程的情况下会工作得很好吗?
发布于 2020-05-17 20:04:32
问题1的答案:
如果没有完整的栅栏,就不会有JMM所要求的顺序一致性。
因此,X86提供了TSO。因此,下面的障碍是免费的LoadLoadStoreStore。唯一缺少的是StoreLoad。
装入具有获取语义
r1=X
[LoadLoad]
[LoadStore]商店具有发布语义
[LoadStore]
[StoreStore]
Y=r2如果你想做一个后跟加载的存储,你最终会得到这样的结果:
[LoadStore]
[StoreStore]
Y=r2
r1=X
[LoadLoad]
[LoadStore]问题是加载和存储仍然可以重新排序,因此它不是顺序一致的;这对于Java内存模型是强制性的。他们唯一能防止这种情况发生的方法就是使用StoreLoad。最符合逻辑的地方是将其添加到写操作中,因为通常读操作比写操作更频繁。
这可以通过MFENCE或lock addl %(RSP),0来完成
问题2的答案:
putLong的问题是,不仅CPU可以重新排序指令,而且编译器可能会以导致指令重新排序的方式更改代码。
例如:如果你要在循环中执行putLong,编译器可能会决定将写出的内容从循环中拉出,并且该值将对其他线程不可见。如果你想要一个低开销的单一写入器性能计数器,你可能想看看putLongRelease/putLongOrdered(oldname)。这将阻止编译器执行上述操作。以及免费获得的X86上的发布语义。
但是很难给你的第二个问题一个万能的解决方案,因为这取决于你的目标是什么。
https://stackoverflow.com/questions/43460725
复制相似问题