易失性变量“读取”是不是与正常读取一样快?

Posted

技术标签:

【中文标题】易失性变量“读取”是不是与正常读取一样快?【英文标题】:Are volatile variable 'reads' as fast as normal reads?易失性变量“读取”是否与正常读取一样快? 【发布时间】:2010-11-08 14:03:14 【问题描述】:

我知道写入 volatile 变量会从所有 CPU 的内存中刷新它,但是我想知道读取 volatile 变量是否与正常读取一样快?

volatile 变量是否可以放在 cpu 缓存中,还是总是从主内存中获取?

【问题讨论】:

您能在问题的某处添加“java”吗?与 C 存在混淆 - 仅设置关键字似乎还不够。 它现在似乎在那里 【参考方案1】:

你真的应该看看这篇文章:http://***er.co.za/blog/2012/09/10/volatile.html。博客文章认为,易失性读取(对于 x86 也是如此)比 x86 上的非易失性读取要慢得多。

测试 1 是对非易失性变量的并行读写。那里 没有可见性机制,读取的结果是 可能陈旧。 测试 2 是对 volatile 变量的并行读取和写入。这并没有具体解决 OP 的问题。但值得注意的是,有争议的 volatile 可能非常缓慢。 测试 3 是在紧密循环中对 volatile 的读取。证明了易失性的语义表明该值可以随着每次循环迭代而改变。因此,JVM 无法优化读取并将其提升出循环。在测试 1 中,该值很可能被读取和存储一次,因此没有发生实际的“读取”。

感谢 Marc Booker 运行这些测试。

【讨论】:

似乎只有在 volatile 竞争时才会出现这种情况。 @pdeva,不,图表清楚地显示了无竞争的易失性读取比非易失性读取要慢。目测,它看起来慢了 2-3 倍。【参考方案2】:

答案在某种程度上取决于架构。在 x86 上,没有与易失性读取相关的额外开销,尽管对其他优化有影响。

JMM cookbook from Doug Lea, see architecture table near the bottom.

澄清一下:读取本身没有任何额外的开销。内存屏障用于确保正确排序。 JSR-133 将四个障碍分类为“LoadLoad、LoadStore、StoreLoad 和 StoreStore”。根据架构的不同,其中一些障碍对应于“无操作”,这意味着不采取任何行动,而其他障碍则需要围栏。负载本身没有隐含成本,但如果设置了栅栏,可能会产生成本。在 x86 的情况下,只有 StoreLoad 屏障会导致栅栏。

正如博客文章中所指出的,变量是 volatile 的事实意味着无法再对变量的性质做出假设,并且某些编译器优化不会应用于 volatile。

易挥发的东西不应该随便使用,但也不应该害怕。在很多情况下,一个 volatile 就足以代替更繁重的锁定。

【讨论】:

怎么会这样?那么多核处理器呢? (不阅读您发布的整个链接) 没有什么是“无操作”,除非它可以从您的程序中完全消除。易失性读取很便宜,但不是免费的,并且会以普通读取所不具备的方式抑制优化(例如,易失性读取不能提升到循环之外,或从寄存器中读取)。这种情况下的“no-op”是指没有对应的栅栏指令,但是读取本身是有语义的,而且是有代价的。 正确。但是,这些是优化语义,超出了 volatile 明确施加的语义。这个问题的主要答案是“它依赖于架构”。目前,评论属实。很多人担心不稳定,这有点愚蠢。虽然我也不建议在所有东西上都使用 volatile。【参考方案3】:

它依赖于架构。 volatile 所做的是告诉编译器不要优化该变量。它强制大多数操作将变量的状态视为未知数。因为它是易失的,它可以被另一个线程或其他一些硬件操作改变。因此,读取将需要重新读取变量,并且操作将属于读取-修改-写入类型。

这种变量用于设备驱动程序,也用于与内存中的互斥体/信号量同步。

【讨论】:

不相关,问题是针对 Java 的。 如果您了解波动性的含义,它仍然是相关的。 在 Java 中的意思略有不同。 @ripper234:当答案 100% 完全可行时,我受够了这些人对答案的评价。上述答案在所有方面都是绝对正确的,即使对于 Java 也是如此! Ripper234,我建议您重新评估您的评论并降级!【参考方案4】:

易失性读取不能那么快,尤其是在多核 CPU(但也仅限于单核)上。 执行内核必须从实际内存地址中获取以确保它获取当前值 - 变量确实无法缓存。

与此处的另一个答案相反,易失性变量不仅仅用于设备驱动程序!它们有时对于编写高性能多线程代码至关重要!

【讨论】:

易失性读取不能那么快,尤其是在多核 CPU 上这完全不正确,在大多数硬件上,易失性读取只是正常负载。缺少缓存时负载很昂贵,但如果缓存中有值,则易失性读取不会进入主内存,甚至易失性写入(代价高昂)可能会更新本地 CPU 缓存行。多 CPU/Socket 依赖于缓存一致性协议来确保有效值,但它不会使任何地方的易失性读取变得更加昂贵。 @bestsss - 好吧,我承认对细节一无所知,但我总是被教导正常读取“总是”优于易失性读取。我不会在答案上赌 1 亿美元。 不,某些 cpu 可能需要加载负载屏障,但它仍然很便宜。根据经验,易失性读取只是正常负载。如果任何 CPU 需要进入主内存(也称为缓存未命中),这是一个可怕的设计缺陷。 这个答案完全不正确,并假设实际 CPU 的行为方式与理论上的想象方式相同。是的,我们想象实际的内核必须从实际的内存地址中获取以确保它获得当前值,但现实世界的 CPU 实际上并没有这样做。他们有很多更好的方法来实现相同的结果,例如强制来自其他 CPU 的写入使其缓存行无效,而不是不缓存。【参考方案5】:

volatile 意味着编译器无法通过将变量的值放入 CPU 寄存器来优化变量。它必须从主存访问。但是,它可以放在 CPU 缓存中。缓存将保证系统中任何其他 CPU/内核之间的一致性。如果内存映射到IO,那么事情就复杂了一些。如果它是这样设计的,硬件将阻止该地址空间被缓存,并且对该内存的所有访问都将转到硬件。如果没有这样的设计,硬件设计人员可能需要额外的 CPU 指令来确保读/写通过缓存等。

通常,“volatile”关键字仅用于操作系统中的设备驱动程序。

【讨论】:

这可能是 volatile 在 C 中的含义,但在 Java 中不是它的含义。在 Java 中,波动性是关于执行读取的一个线程是否会“看到”另一个线程所做的更改。这不仅仅是该值是否可以在 CPU 寄存器中。 volatile 关键字还可以防止 JVM 对使用该变量的代码进行哪些类型的重新排序。 这是 Dobb 博士的一篇文章,详细介绍了不同之处:ddj.com/hpc-high-performance-computing/212701484 读者没有说java,特别提到二级缓存,所以我假设最常见的场景...... C. 我不知道为什么这个答案被否决,但 Java 中的 volatile 关键字提供了相同的功能。基本上,该变量不会被缓存并写入和读取内存,因此每个线程都可以看到更改。

以上是关于易失性变量“读取”是不是与正常读取一样快?的主要内容,如果未能解决你的问题,请参考以下文章

非易失性MRAM数据写入与读取

易失性变量和非易失性重新排序/可见性

对具有“易失性”属性的动态分配变量的内存访问是不是会导致每次访问的缓存未命中?

易失性和缓存行为

Java 内存模型:易失性变量和发生前

这个 MSDN CompareExchange 示例如何不需要易失性读取?