我在这个 SO 中读到了 [[carries_dependency]]发布。
但我无法理解的是接受的答案中的以下句子:
"In particular, if a value read with memory_order_consume is passed in to a function, then without [[carries_dependency]], then the compiler may have to issue a memory fence instruction to guarantee that the appropriate memory ordering semantics are upheld. If the parameter is annotated with [[carries_dependency]] then the compiler can assume that the function body will correctly carry the dependency, and this fence may no longer be necessary.
Similarly, if a function returns a value loaded with memory_order_consume, or derived from such a value, then without [[carries_dependency]] the compiler may be required to insert a fence instruction to guarantee that the appropriate memory ordering semantics are upheld. With the [[carries_dependency]] annotation, this fence may no longer be necessary, as the caller is now responsible for maintaining the dependency tree."
让我们一步一步来:
"if a value read with memory_order_consume is passed in to a function, then without [[carries_dependency]], then the compiler may have to issue a memory fence instruction to guarantee that the appropriate memory ordering semantics are upheld."
因此,对于释放-消耗内存模型中的原子变量,当原子变量作为参数传递给函数时,编译器将引入栅栏硬件指令,以便它始终具有提供给的原子变量的最新和更新值函数。
下一步 -
"If the parameter is annotated with [[carries_dependency]] then the compiler can assume that the function body will correctly carry the dependency, and this fence may no longer be necessary."
这让我感到困惑 - 原子变量值已经被消耗,然后该函数携带什么依赖项?
同样-
"if a function returns a value loaded with memory_order_consume, or derived from such a value, then without [[carries_dependency]] the compiler may be required to insert a fence instruction to guarantee that the appropriate memory ordering semantics are upheld. With the [[carries_dependency]] annotation, this fence may no longer be necessary, as the caller is now responsible for maintaining the dependency tree."
从示例中不清楚它试图说明携带依赖项的要点是什么?
最佳答案
仅供引用,memory_order_consume
(和 [[carries_dependency]]
)基本上已被弃用,因为编译器很难有效和正确地实现 C++11 设计规则的方式。 (和/或因为 [[carries_dependency]]
和/或 kill_dependency
最终到处都需要。)见 P0371R1: Temporarily discourage memory_order_consume .
当前的编译器只处理 mo_consume
作为mo_acquire
(因此在需要一个的 ISA 上,在消耗负载之后放置一个屏障)。如果你想要无障碍的数据依赖排序性能,你必须通过使用 mo_relaxed
来欺骗编译器。并仔细编码以避免编译器可能在没有实际依赖的情况下创建 asm 的事情。 (例如 Linux RCU)。参见 C++11: the difference between memory_order_relaxed and memory_order_consume有关更多详细信息和链接,以及 mo_consume
的 asm 功能旨在揭露。
还有 Memory order consume usage in C11 .
了解依赖排序的概念(在 asm 中)对于理解此 C++ 功能的设计方式基本上是必不可少的。
When [an] atomic variable is being passed as a parameter to the function the compiler will introduce a fence hardware instruction ...
首先,您不会“将原子变量传递给”函数;那甚至意味着什么?如果您传递一个指针或对原子对象的引用,该函数将从它自己加载,并且该函数的源代码将使用 memory_order_consume
还是不是。
相关的事情是从带有 mo_consume 的原子变量加载传递一个值。像这样:
int tmp = shared_var.load(std::memory_order_consume);
func(tmp);
func
可以使用该 arg 作为 atomic<int>
数组的索引做一个mo_relaxed
加载。对于要在 shared_var.load
之后依赖排序的负载即使没有内存屏障,func
的代码生成必须确保加载对 arg 具有 asm 数据依赖性,即使 C++ 代码执行类似 tmp -= tmp;
的操作也是如此。编译器通常只会将其视为 tmp = 0;
(杀死以前的值(value))。
但是[[carries_dependency]]
将使编译器在实现类似于 array[idx+tmp]
的东西时仍然引用具有数据依赖性的归零值.
the atomic variable value is already consumed and then what dependency the function is carried?
“已消费”不是一个有效的概念。整点consume
而不是 acquire
是因为它们对 mo_consume
有 data 依赖性,所以后面的加载顺序正确加载结果,让您避开障碍。如果您希望它在原始加载之后排序,那么以后的每个加载都需要这样的依赖关系;没有任何意义可以说某个值“已被消耗”。
如果由于某个函数缺少 carrys_dependency 而最终插入一个屏障来促进 consume 获取,则后面的函数将不需要另一个屏障,因为您可以说该值“已经获取”。 (尽管这不是标准术语。您会在加载后订购第一个障碍后说代码。)
了解 Linux 内核如何处理这个问题可能会很有用,因为它们使用手动滚动的原子操作和它们支持的有限编译器集。在中搜索“依赖项”
https://github.com/torvalds/linux/blob/master/Documentation/memory-barriers.txt ,并注意像 if(flag) data.load()
这样的“控制依赖”之间的区别与 data[idx].load
之类的数据依赖关系.
IIRC,即使是 C++ 也不能保证 mo_consume
当依赖项是条件条件时的依赖项排序,如 if(x.load(consume)) tmp=y.load();
.
请注意,如果只有 2 个可能的值,编译器将有时会将数据依赖项转换为控制依赖项。这会破坏 mo_consume
, 如果值来自 mo_consume
则不允许进行优化加载或 [[carries_dependency]]
函数参数。这就是为什么难以实现的部分原因;这将需要教授大量关于数据依赖排序的优化过程,而不是仅仅期望用户编写不执行通常会优化掉的事情的代码。 (喜欢tmp -= tmp;
)
关于c++ - [[carries_dependency]] 什么意思以及如何实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64113244/