【问题标题】:C11 Standalone memory barriers LoadLoad StoreStore LoadStore StoreLoadC11 独立内存屏障 LoadLoad StoreStore LoadStore StoreLoad
【发布时间】:2020-05-10 10:55:08
【问题描述】:

我想在原子操作和非原子操作之间使用独立内存屏障(我认为无论如何这都不重要)。我想我了解存储屏障和负载屏障的含义,以及 4 种可能的内存重新排序; LoadLoad、StoreStore、LoadStore、StoreLoad。

但是,我总是觉得获取/发布概念令人困惑。因为在阅读文档的时候,acquire 不只讲loads,还讲stores,release 不只讲stores,还讲loads。另一方面,普通负载障碍仅向您提供负载保证,而普通商店障碍仅向您提供商店保证。

我的问题如下。在 C11/C++11 中,将独立的 atomic_thread_fence(memory_order_acquire) 视为负载屏障(防止 LoadLoad 重新排序)和将 atomic_thread_fence(memory_order_release) 作为存储屏障(防止 StoreStore 重新排序)是否安全?

如果上述内容正确,我可以使用什么来防止 LoadStore 和 StoreLoad 重新排序?

当然,我对可移植性感兴趣,我不在乎上述内容在特定平台上产生了什么。

【问题讨论】:

  • 别忘了atomic_thread_fence只是定义了WRT原子操作。
  • @curiousguy 但是atomic_thread_fence 是一个独立的屏障。我看不出类型的原子性和原子操作在这里起什么作用。内存屏障只是为您在屏障之前和/或之后的所有内容提供排序保证。
  • 不使用原子能提供什么保证?
  • @curiousguy 订购保证?某些操作在其他操作之前完成?
  • @curiousguy 甚至文档都说“排序非原子和宽松的原子访问”:en.cppreference.com/w/c/atomic/atomic_thread_fence

标签: c++ c memory-barriers memory-model stdatomic


【解决方案1】:

不,获取障碍在轻松加载后会变成获取加载(在某些 ISA 上与仅使用获取加载相比效率低下),因此它也必须阻止 LoadStore作为LoadLoad。

请参阅https://preshing.com/20120913/acquire-and-release-semantics/ 了解一些非常有用的顺序图,这些图显示了发布存储需要确保所有先前的加载和存储都是“可见的”,因此需要阻止 StoreStore 和 LoadStore。 (商店部分为第二的重新排序)。尤其是这张图:

还有https://preshing.com/20130922/acquire-and-release-fences/

https://preshing.com/20131125/acquire-and-release-fences-dont-work-the-way-youd-expect/ 解释了 acq 和 rel fences 的 2 向性质与 acq 或 rel 操作(如加载或存储)的 1 向性质。显然有些人对atomic_thread_fence() 保证的内容有误解,认为它太弱了。

为了完整起见,请记住这些排序规则必须由编译器针对compile-time reordering 强制执行,而不仅仅是运行时。

考虑作用​​于 C++ 抽象机中的 C++ 加载/存储的障碍可能最有效,不管它是如何在 asm 中实现的。但在 PowerPC 等极端案例中,思维模型并不能涵盖所有内容(IRIW 重新排序,见下文)。

我确实建议尝试从获取和释放操作的角度来考虑,以确保其他操作对彼此的可见性,并且绝对不要编写仅使用宽松操作和单独障碍的代码。安全,但通常效率较低。


关于 ISO C/C++ 内存/线程间排序的一切正式定义为获取负载从发布存储中查看值,从而创建“同步”关系,而不是关于控制本地重新排序的栅栏。

std::atomic 确实不明确保证所有线程同时看到变化的一致共享内存状态的存在。在您使用的心智模型中,通过在读取/写入单个共享状态时进行本地重新排序,IRIW 重新排序可能发生在一个线程使其存储对一些其他线程可见之前,它们变得全局可见所有其他线程。 (喜欢可以happen in practice on some SMT PowerPC CPUs.)。

在实践中all C/C++ implementations run threads across cores that do have a cache-coherent view of shared memory 因此,在读/写连贯共享内存方面的心智模型具有控制本地重新排序的障碍。但请记住,C++ 文档不会讨论 re 排序,只是讨论是否首先保证任何顺序。


要深入了解 C++ 如何描述内存模型与如何描述真实架构的 asm 内存模型之间的区别,另请参阅How to achieve a StoreLoad barrier in C++11?(包括我的答案)。 Does atomic_thread_fence(memory_order_seq_cst) have the semantics of a full memory barrier? 也是相关的。

fence(seq_cst) 包括 StoreLoad(如果该概念甚至适用于给定的 C++ 实现)。我认为根据局部障碍进行推理,然后将其转换为 C++大部分是可行的,但请记住,它没有模拟 C++ 允许的 IRIW 重新排序的可能性,以及在现实生活中某些 POWER 上发生的可能性硬件。

另外请记住,在某些 ISA(尤其是 ARMv8)上,var.load(acquire) 可能比 var.load(relaxed); fence(acquire); 更高效。

例如this example on Godbolt,由 GCC8.2 为 ARMv8 编译 -O2 -mcpu=cortex-a53

#include <atomic>
int bad_acquire_load(std::atomic<int> &var){
    int ret = var.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);
    return ret;
}

bad_acquire_load(std::atomic<int>&):
        ldr     r0, [r0]          // plain load
        dmb     ish               // FULL BARRIER
        bx      lr
int normal_acquire_load(std::atomic<int> &var){
    int ret = var.load(std::memory_order_acquire);
    return ret;
}

normal_acquire_load(std::atomic<int>&):
        lda     r0, [r0]            // acquire load
        bx      lr

【讨论】:

  • 所以,回顾一下。在实践中,假设获取栅栏总是至少充当负载屏障,而释放栅栏至少充当存储屏障,是否安全?现代 C/C++ 中是否还有其他可用的东西仅充当 LoadLoad 或 StoreStore 屏障?
  • @ilstam:是的,至少在为 x86 或 ARM 等 ISA 编译时,因此整个讨论可以有任何意义,获取栅栏必须始终包含 LoadLoad 屏障。不,在可移植的 ISO C++ 中没有任何东西只有 LoadLoad 而没有 LoadStore。无论如何,大多数 CPU 都没有这样的障碍。例如PowerPC 提供lwsync,这是所有 3 个便宜的(不是 StoreLoad)。
  • 对于某些给定的实现,您可能可以使用内联汇编。例如x86 sfence 仅是 StoreStore,它影响 NT 存储以及普通存储。 (普通商店是强烈订购的,所以在 NT 商店之外,sfence 是无用的)。 ARM dsb 实际上可能有办法只使用 LoadLoad 而没有 LoadStore(这可能是编译器必须使用完整屏障 dsb ish 的原因,即使是 atomic_thread_fence(mo_acquire)。godbolt.org/z/jAwEKX)
  • 最后atomic_thread_fence(memory_order_seq_cst) 会以可移植的方式阻止StoreLoad 吗?或者这在 C/C++ 标准的说法中没有意义?
  • @ilstam:是的,fence(seq_cst) 在为正常 ISA 编译时包含 StoreLoad,其内存模型根据本地栅栏工作。 ISO C++ 没有这样说明,但实际上这就是在 ISA 上发生的情况,其中整个共享内存访问重新排序模型都适用。 How to achieve a StoreLoad barrier in C++11? 对此有一些很好的答案,还有Does atomic_thread_fence(memory_order_seq_cst) have the semantics of a full memory barrier?
猜你喜欢
  • 2014-02-04
  • 2020-01-30
  • 2013-02-27
  • 2014-11-16
  • 2016-05-20
  • 1970-01-01
  • 1970-01-01
  • 2011-09-28
  • 2011-07-05
相关资源
最近更新 更多