[[carries_dependency]] 什么意思以及如何实现
[[carries_dependency]] what it means and how to implement
我在这篇 SO post 中阅读了有关 [[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 设计规则的方式。 (And/or 因为 [[carries_dependency]]
and/or kill_dependency
最终到处都需要。)见 P0371R1: Temporarily discourage memory_order_consume.
当前的编译器只是将 mo_consume
视为 mo_acquire
(因此在需要一个的 ISA 上,在消耗负载之后放置一个屏障)。如果您想要无障碍地执行数据依赖排序,则必须通过使用 mo_relaxed
来欺骗编译器,并仔细编写代码以避免编译器可能在没有实际依赖的情况下创建 asm。 (例如 Linux RCU)。有关详细信息和相关链接,以及 mo_consume
旨在公开的 asm 功能,请参阅 C++11: the difference between memory_order_relaxed and memory_order_consume。
还有.
理解依赖排序的概念(在 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 的原子变量传递值 loaded。像这样:
int tmp = shared_var.load(std::memory_order_consume);
func(tmp);
func
可以使用该 arg 作为 atomic<int>
数组的索引来执行 mo_relaxed
加载。对于 shared_var.load
之后的负载 dependency-ordered 即使没有内存屏障, code-gen for func
必须确保负载对 arg 具有 asm 数据依赖性,即使如果 C++ 代码执行类似 tmp -= tmp;
的操作,编译器通常会将其视为与 tmp = 0;
相同(杀死先前的值)。
但是 [[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 依赖性,让你避开障碍。如果您希望它在原始加载之后排序,那么以后的每个加载都需要这样的依赖关系;说一个值“已经消耗”是没有意义的。
如果你最终插入了一个屏障来促进消费获取,因为缺少一个函数 carries_dependency,后面的函数将不需要另一个屏障,因为你可以说该值“已经获得” . (尽管这不是标准术语。您会在加载后订购第一个障碍后说代码。)
了解 Linux 内核如何使用它们的 hand-rolled 原子和它们支持的有限编译器集来处理这个问题可能会很有用。在中搜索“依赖项”
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]]
函数 arg,则不允许进行优化。这就是为什么难以实施的部分原因;这将需要教授大量关于数据依赖排序的优化过程,而不是仅仅期望用户编写不执行通常会优化掉的事情的代码。 (喜欢tmp -= tmp;
)
我在这篇 SO post 中阅读了有关 [[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 设计规则的方式。 (And/or 因为 [[carries_dependency]]
and/or kill_dependency
最终到处都需要。)见 P0371R1: Temporarily discourage memory_order_consume.
当前的编译器只是将 mo_consume
视为 mo_acquire
(因此在需要一个的 ISA 上,在消耗负载之后放置一个屏障)。如果您想要无障碍地执行数据依赖排序,则必须通过使用 mo_relaxed
来欺骗编译器,并仔细编写代码以避免编译器可能在没有实际依赖的情况下创建 asm。 (例如 Linux RCU)。有关详细信息和相关链接,以及 mo_consume
旨在公开的 asm 功能,请参阅 C++11: the difference between memory_order_relaxed and memory_order_consume。
还有
理解依赖排序的概念(在 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 的原子变量传递值 loaded。像这样:
int tmp = shared_var.load(std::memory_order_consume);
func(tmp);
func
可以使用该 arg 作为 atomic<int>
数组的索引来执行 mo_relaxed
加载。对于 shared_var.load
之后的负载 dependency-ordered 即使没有内存屏障, code-gen for func
必须确保负载对 arg 具有 asm 数据依赖性,即使如果 C++ 代码执行类似 tmp -= tmp;
的操作,编译器通常会将其视为与 tmp = 0;
相同(杀死先前的值)。
但是 [[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 依赖性,让你避开障碍。如果您希望它在原始加载之后排序,那么以后的每个加载都需要这样的依赖关系;说一个值“已经消耗”是没有意义的。
如果你最终插入了一个屏障来促进消费获取,因为缺少一个函数 carries_dependency,后面的函数将不需要另一个屏障,因为你可以说该值“已经获得” . (尽管这不是标准术语。您会在加载后订购第一个障碍后说代码。)
了解 Linux 内核如何使用它们的 hand-rolled 原子和它们支持的有限编译器集来处理这个问题可能会很有用。在中搜索“依赖项”
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]]
函数 arg,则不允许进行优化。这就是为什么难以实施的部分原因;这将需要教授大量关于数据依赖排序的优化过程,而不是仅仅期望用户编写不执行通常会优化掉的事情的代码。 (喜欢tmp -= tmp;
)