【问题标题】:difference in mfence and asm volatile ("" : : : "memory")mfence 和 asm volatile ("" : : : "memory") 的区别
【发布时间】:2012-08-24 09:08:14
【问题描述】:

据我了解,mfence 是硬件内存屏障,而asm volatile ("" : : : "memory") 是编译器屏障。但是,asm volatile ("" : : : "memory") 可以用来代替 mfence。

我困惑的原因是this link

【问题讨论】:

  • 你要为什么 CPU 编译? x86/x64?
  • 我同时使用 x86 和 x64。 x86 和 x64 机器的答案是否应该不同?
  • 嗯,只有在内存排序较弱的架构上才需要内存屏障。 x86 和 x64 没有弱内存排序。在 x86/x64 上,所有存储都有一个释放栅栏,所有负载都有一个获取栅栏。所以,你应该只需要 asm volatile ("" : : : "memory")
  • “在 x86/x64 上,所有存储都有一个释放栅栏,所有加载都有一个获取栅栏”。你能指点我一些关于这个的相关文档吗?你也可以这样回答,我会接受,因为这会回答我的问题:)

标签: gcc x86 memory-barriers


【解决方案1】:

嗯,只有在内存排序较弱的架构上才需要内存屏障。 x86 和 x64 没有弱内存排序。在 x86/x64 上,所有存储都有一个释放栅栏,所有负载都有一个获取栅栏。所以,你应该只需要asm volatile ("" : : : "memory")

如需全面了解 Intel 和 AMD 以及相关制造商规格的参考资料,请参阅 http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/

通常在每个字段的基础上使用“volatile”之类的内容,其中对该字段的加载和存储是本机原子的。如果对字段的加载和存储已经是原子的(即,所讨论的“操作”是对单个字段的加载或存储,因此整个操作是原子的)在 x86 上不需要 volatile 字段修饰符或内存屏障/x64。尽管有可移植代码。

当涉及到非原子的“操作”时——例如。加载或存储到比本地单词更大的字段或加载或存储到“操作”中的多个字段 - 无论 CPU 如何,都需要一种可以将操作视为原子的方法建筑学。 通常这是通过像互斥锁这样的同步原语来完成的。互斥体(我使用的那些)包括内存屏障以避免处理器重新排序等问题,因此您不必添加额外的内存屏障指令。我通常认为不使用同步原语是过早的优化;但是,过早优化的本质当然是 97% 的时间:)

如果您不使用同步原语并且您正在处理多字段不变量,那么确保处理器不会重新排序存储和加载到不同内存位置的内存屏障很重要。

现在,关于不在 asm volatile 中发出“mfence”指令,而是在 clobber 列表中使用“memory”。据我所知read

如果您的汇编指令以不可预知的方式访问内存,请将“内存”添加到被破坏的寄存器列表中。这将导致 GCC 不会在整个汇编程序指令中将内存值缓存在寄存器中,并且不会优化对该内存的存储或加载。

当他们说“GCC”而没有提及 CPU 时,这意味着它仅适用于编译器。缺少“mfence”意味着没有 CPU 内存屏障。您可以通过反汇编生成的二进制文件来验证这一点。如果没有发出“mfence”指令(取决于目标平台),那么很明显没有通知 CPU 发出内存栅栏。

根据您所使用的平台和您正在尝试做的事情,可能会有一些“更好”或更清晰的东西......可移植性无法承受。

【讨论】:

  • +1 这是 99.9% 的正确率,除了存储到 不同 位置的存储在多核系统中是无序的(如果需要,则需要 MFENCE)。但是,这通常是“是的 WTF ......谁在乎?”事物。同一个核心上的指令始终按照它们执行的顺序实现,并且在不同核心上加载/存储到同一位置具有您所描述的保证。
  • @Peter 感谢您发布此链接。我之前也提到过它,我的怀疑源于彼得森锁问题。因此,作者提到“可能会将旧商店的负载重新排序到不同的位置”,这可能会破坏彼得森算法的实现,并且需要 mfence 才能正确实现它。但是,“asm volatile”也足够了吗?因为它只是一个编译器屏障,并且在维基百科上也提到过(en.wikipedia.org/wiki/… 屏障阻止编译器重新排序,而不是 CPU 重新排序。
  • @neal 我以为你在谈论一个你想跨线程/CPU同步的字段......我已经在我的回答中添加了一些细节w.r.t。非原子“操作”。
  • 第一段全错了:嗯,只有在内存排序较弱的架构上才需要内存屏障。 x86 和 x64 没有弱内存排序。在 x86/x64 上,所有存储都有一个释放栅栏,所有负载都有一个获取栅栏。所以,你应该只需要 asm volatile ("" : : : "memory") 这意味着 x86 不需要(硬件)内存屏障,只需要像 volatile("" : : : "memory") 这样的编译器屏障,对吗?那是错的。 x86 内存相对强大,但肯定会一直重新排序,并且_一直都需要硬件屏障。
  • 我知道那篇旧论文,当前 SDM 的第 3 卷很好地涵盖了它(但 IMO something like this 甚至更好)——但我们并没有真正争论x86内存模型是我们吗?我认为我们(至少我是)只是在争辩说,您在这个公认答案的第一段中的第一个也是最重要的主张是完全错误的。我什至不需要详细阅读其余部分,因为“x86 强大,您可以摆脱编译器障碍”的总体原则已经是错误的......
【解决方案2】:
  • asm volatile ("" ::: "memory") 只是一个编译器障碍。
  • asm volatile ("mfence" ::: "memory") 既是编译器屏障,又是 MFENCE
  • __sync_synchronize() 也是编译器屏障和完整内存屏障。

所以asm volatile ("" ::: "memory") 不会阻止 CPU 重新排序独立数据指令本身。正如所指出的 x86-64 具有强大的内存模型,但 StoreLoad 重新排序仍然是可能的。如果你的算法需要一个完整的内存屏障,那么你需要__sync_synchronize

【讨论】:

    【解决方案3】:

    有两种重新排序,一种是编译器重新排序,另一种是CPU重新排序。

    x86/x64 具有相对强大的内存模型,但在 x86/x64 上,StoreLoad 重新排序(稍后加载通过更早的存储)可能会发生。 见http://en.wikipedia.org/wiki/Memory_ordering

    • asm volatile ("" ::: "memory") 只是一个编译器障碍。
    • asm volatile ("mfence" ::: "memory") 既是编译器屏障,也是 CPU 屏障。

    也就是说,只使用编译器屏障,只能阻止编译器重新排序,但不能阻止 CPU 重新排序。这意味着在编译源代码时没有重新排序,但重新排序可以在运行时发生。

    所以,这取决于您的需求,使用哪一个。

    【讨论】:

      猜你喜欢
      • 2013-01-05
      • 1970-01-01
      • 2016-05-16
      • 1970-01-01
      • 1970-01-01
      • 2021-08-30
      • 2011-06-03
      • 1970-01-01
      • 2019-07-20
      相关资源
      最近更新 更多