【问题标题】:compiler reordering vs memory reordering编译器重新排序与内存重新排序
【发布时间】:2014-12-14 17:30:09
【问题描述】:

在 gcc 下,有以下指令可用于设置内存屏障。它们都提供不同的“保护”

asm volatile("" ::: "memory"); // compiler reorder
asm volatile("mfence" ::: "memory"); // memory reordering

简而言之,C++ 原子提供:

- acquire/release semantics
- Sequentially-consistent ordering

我想知道 gcc 原语和 C++ 原子语义之间是否存在直接映射? (例如(一定是错误的,只是为了解释),获取/释放语义是为了防止编译器重新排序,而顺序一致的排序是为了防止内存重新排序)

或者也许 C++ 没有这个区别?该语言仅提供适用于同时重新排序的语义?

【问题讨论】:

  • 要实现 C++ 原子,编译器必须以正确的顺序发出代码指令(即避免某些可用于非原子的优化),知道目标硬件的内存排序和可见性,并可能发出额外的指令以实现 C++ 做出的预期保证。

标签: c++ gcc atomic


【解决方案1】:

第一个障碍仅在编译期间适用。一旦编译器完成,它就没有任何影响,因为没有任何东西添加到代码中。这对于避免一些内存排序问题可能很有用(编译器不知道其他线程如何操作这些内存位置,尽管几乎没有任何具有正常设置的编译器敢于对有可能的变量重新排序)。

但是,这还远远不够,因为在现代乱序 CPU 上,硬件本身可能会在后台重新排序操作。为了避免这种情况,你有办法告诉硬件当心,给定你想要达到的确切级别和限制形式(顺序一致性是最严格和“安全”的排序模型,但通常也是最昂贵的性能)。

要实现这些限制,您可以尝试手动维护 ISA 提供的屏障和类似构造(通过内在函数、内联汇编、序列化操作或任何其他技巧)。即使您知道自己在做什么,这通常也很复杂,甚至可能是特定于微架构的(某些 CPU 可能会“免费”授予一些限制,从而使显式围栏无用),因此 c++11 将原子语义添加到使这项任务更容易,现在编译器会根据您想要的指定排序模型为您添加必要的代码。

在您的示例中,mfence 是手动执行操作的示例,但您还需要知道在哪里应用它。如果使用得当,mfence 可以是足够字符串以提供 seq 一致性,但也非常昂贵,因为它包含一个存储栅栏 (mfence = sfence + lfence),这需要从内部缓冲区中排出所有待处理的存储,这是一个缓慢的操作,因为完成了缓冲以允许它们进行延迟提交。 另一方面,如果您想要获取/释放语义,您可以选择在考虑到您的架构的正确位置使用适当的部分围栏来实现它们,或者让编译器为您完成。例如,如果您选择后者并在 x86 机器上运行,您会发现大多数时候不需要添加任何内容,因为存储具有隐式释放语义,加载具有获取语义,但同样可能不适用于其他架构。

这里是对每个架构的各种排序语义的实现的一个很好的总结 - http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-02
    • 1970-01-01
    • 2016-10-10
    • 1970-01-01
    • 1970-01-01
    • 2019-01-01
    • 2019-01-22
    • 1970-01-01
    相关资源
    最近更新 更多