gpt4 book ai didi

c++ - memory_order_consume 到底有什么作用?

转载 作者:行者123 更新时间:2023-12-03 10:05:04 25 4
gpt4 key购买 nike

从链接:
What is the difference between load/store relaxed atomic and normal variable?
这个回答给我留下了深刻的印象:

Using an atomic variable solves the problem - by using atomics allthreads are guarantees to read the latest writen-value even if thememory order is relaxed.


今天,我阅读了以下链接:
https://preshing.com/20140709/the-purpose-of-memory_order_consume-in-cpp11/
atomic<int*> Guard(nullptr);
int Payload = 0;
线程1:
  Payload = 42;
Guard.store(&Payload, memory_order_release);
线程2:
g = Guard.load(memory_order_consume);
if (g != nullptr)
p = *g;
enter image description here
问题:
我了解到 数据依赖 防止相关指令被重新排序。
但我认为这对于确保执行结果的正确性是显而易见的。 comsume-release 语义是否存在并不重要。
所以我想知道 comsum-release 真的可以。哦,也许它使用数据依赖来防止指令重新排序,而 确保有效载荷的可见性 ?
所以
如果我使 1.preventing 指令重新排序,是否可以使用 memory_order_relaxed 获得相同的正确结果
2.确保Payload的非原子变量的可见性
:
atomic<int*> Guard(nullptr);
volatile int Payload = 0; // 1.Payload is volatile now

// 2.Payload.assign and Guard.store in order for data dependency
Payload = 42;
Guard.store(&Payload, memory_order_release);

// 3.data Dependency make w/r of g/p in order
g = Guard.load(memory_order_relaxed);
if (g != nullptr)
p = *g; // 4. For 1,2,3 there are no reorder, and here, volatile Payload make the value of 42 is visable.
附加内容(由于 Sneftel 的回答):
1.有效载荷=42;
volatile 使有效负载的 W/R 到/从主内存而不是/从缓存。因此 42 将写入内存。
2.Guard.store(&Payload,任何MO标志都可以写);正如您所说,Guard 是非 volatile 的,但它是原子的

Using an atomic variable solves the problem - by using atomics allthreads are guarantees to read the latest writen-value even if thememory order is relaxed.

In fact, atomics are always thread safe, regardless of the memoryorder! The memory order is not for the atomics -> it's for non atomicdata.


所以Guard.store执行后,Guard.load(带有任何MO标志都可以读取)可以正确的获取到Payload的地址。然后正确地从内存中获取 42。
以上代码:
1.对数据依赖没有重新排序的影响。
2. 对 volatile Payload 没有缓存效果
3. atomic Guard 没有线程安全问题
我能得到正确的值 - 42吗?
回到主要问题

When you use consume semantics, you’re basically trying to make thecompiler exploit data dependencies on all those processor families.That’s why, in general, it’s not enough to simply changememory_order_acquire to memory_order_consume. You must also make surethere are data dependency chains at the C++ source code level.


enter image description here
“您还必须确保在 C++ 源代码级别存在数据依赖链。”
我认为 C++ 源代码级别的数据依赖链会阻止指令自然重新排序。
那么 memory_order_consume 到底有什么作用呢?
我可以使用 memory_order_relaxed 来实现与上述代码相同的结果吗?
附加内容结束

最佳答案

首先,memory_order_consume ISO C++ 委员会暂时不鼓励,直到他们提出编译器可以实际实现的东西。几年来,编译器处理过 consume作为 acquire 的同义词. 请参阅此答案底部的部分。
硬件仍然提供数据依赖性,因此尽管目前没有任何安全可移植的 ISO C++ 方法可以利用,但讨论这一点很有趣。 (仅使用 mo_relaxed 或手动原子进行 hack,以及基于对编译器优化和 asm 的理解的仔细编码,有点像您尝试使用轻松的方式。但您不需要 volatile。)

Oh, maybe it uses data dependencies to prevent reordering of instructions while ensuring the visibility of Payload?


不完全是“指令重新排序”,而是内存重新排序。正如您所说,如果硬件提供依赖性排序,那么在这种情况下,理智和因果关系就足够了。 C++ 可移植到不能移植的机器上。 (例如 DEC Alpha。)
获得有效负载可见性的正常方法是通过写入器中的发布存储,在读取器中获取负载 它可以看到该发布商店的值(value)。 https://preshing.com/20120913/acquire-and-release-semantics/ . (当然,反复将相同的值存储到“ready_flag”或指针不会让读者弄清楚它是看到新商店还是旧商店。)
Release/Acquire 在线程之间创建了一个happens-before 同步关系,这保证了作者在release-store 之前所做的一切的可见性。 (消费没有,这就是为什么只订购相关负载。)
( consume 是对此的优化:只要您遵循一些依赖性规则,就可以让编译器利用硬件保证来避免读取器中的内存障碍。)

您对什么是 CPU 缓存以及什么是 volatile 有一些误解。确实,我在问题下评论过。发布存储确保早期的非原子分配在内存中可见。
(此外,缓存是一致的;它为所有 CPU 提供了他们可以达成一致的共享内存 View 。寄存器是线程私有(private)的并且不一致,这就是人们所说的“缓存”值的意思。寄存器不是 CPU缓存,但软件可以使用它们来保存内存中某些内容的拷贝。 When to use volatile with multi threading? - 永远不会,但它在真实 CPU 中确实有一些影响,因为它们具有一致的缓存。滚动自己的 mo_relaxed 是一种糟糕的方式。见还有 https://software.rajivprab.com/2018/04/29/myths-programmers-believe-about-cpu-caches/ )
在实际 CPU 上,内存重新排序发生在每个内核本地;缓存本身是连贯的,永远不会“不同步”。 (其他拷贝在商店变得全局可见之前无效)。所以 release只需要确保本地 CPU 存储以正确的顺序全局可见(提交到 L1d 缓存)。 ISO C++ 没有指定任何该级别的详细信息,并且假设可能会有一个工作方式非常不同的实现。
使编写器的存储变得易变在实践中是无关紧要的,因为非原子分配后跟一个发布存储已经必须使其他线程可以看到所有内容,这些线程可能会执行获取加载并与该发布存储同步。在纯 ISO C++ 中,它在纸面上无关紧要,因为它不能避免数据竞争 UB。
(当然,对于整个程序优化来说,理论上有可能看到没有获取或消耗加载会加载这个存储,并优化释放属性。但是编译器目前通常甚至在本地也不会优化原子,并且永远不要尝试做那种完整的程序分析。因此编写器函数的代码生成将假设可能有一个读取器与任何给定的发布存储或 seq_cst 排序同步。)

What does memory_order_consume really do?


一件事 mo_consume这样做是为了确保编译器在底层硬件不自然/免费提供依赖顺序的实现上使用屏障指令。实际上,这意味着仅在 DEC Alpha 上。 Dependent loads reordering in CPU/ Memory order consume usage in C11
您的问题与 C++11: the difference between memory_order_relaxed and memory_order_consume 几乎重复- 请参阅那里的答案,了解有关错误尝试以不稳定和放松的方式做事的问题的正文。 (我主要是因为标题问题而回答。)
它还确保编译器在执行传递到不知道该值携带的数据依赖性的代码之前的某个时刻使用屏障。 (即声明中的函数 arg 上没有 [[carries_dependency]] 标签)。这样的代码可能会取代 x-x具有常数 0并优化掉,失去数据依赖性。但是知道依赖关系的代码必须使用类似 sub r1, r1, r1 的东西。获取具有数据依赖性的零的指令。
这不会发生在您的用例中(其中 relaxed 将在 Alpha 以外的 ISA 上实际工作),但是 mo_consume 的纸上设计允许需要与编译器通常会做的不同的代码生成的各种东西。这是使高效实现如此困难的部分原因,编译器只是将其提升为 mo_acquire .
问题的另一部分是它要求代码中到处都是 kill_dependency和/或 [[carries_dependency]]到处都是,否则你最终会在函数边界处遇到障碍。这些问题导致 ISO C++ 委员会暂时劝阻 consume .
  • C++11: the difference between memory_order_relaxed and memory_order_consume
  • P0371R1: Temporarily discourage memory_order_consume和其他 C++ wg21 文档链接的关于为什么不鼓励消费。
  • Memory order consume usage in C11 - 更多关于硬件机制/保证consume旨在暴露给软件。乱序 exec 无论如何只能重新排序独立的工作,而不是在知道加载地址之前开始加载,所以在大多数 CPU 上强制执行依赖排序无论如何都是免费的:只有少数 DEC Alpha 模型可能违反因果关系并有效加载数据从之前它有给它地址的指针。

  • 顺便说一句:
    示例代码是安全的 release + consume不管波动。在实践中使用 release 在大多数编译器和大多数 ISA 上都是安全的商店 + relaxed加载,当然 ISO C++ 没有说明该代码的正确性。但就目前的编译器状态而言,这是某些代码(如 Linux 内核的 RCU)造成的。
    如果您需要那种级别的读取端缩放,您将不得不在 ISO C++ 保证的范围之外工作。这意味着您的代码将不得不对编译器的工作方式做出假设(并且您在非 DEC Alpha 的“正常”ISA 上运行),这意味着您需要支持某些编译器集(可能还有 ISA,尽管周围没有很多多核 ISA)。 Linux 内核只关心少数编译器(主要是最近的 GCC,我认为也是 clang),以及它们拥有内核代码的 ISA。

    关于c++ - memory_order_consume 到底有什么作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65336409/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com