什么时候不应该使用 [[carries_dependency]]?

Mat*_*ton 5 c++ multithreading memory-model carries-dependency stdatomic

我发现了一些问题(比如这个)问什么[[carries_dependency]]是,这不是我在这里问的。

我想知道你什么时候不应该使用它,因为我读过的所有答案都让人觉得你可以把这段代码贴在任何地方,而且你会神奇地得到相等或更快的代码。一个评论说代码可以相等或更慢,但海报没有详细说明。

我想在任何函数返回或参数上使用 this 的合适位置是指针或引用,并且将在调用线程内传递或返回,并且不应在回调或线程入口点上使用它。

有人可以评论我的理解并详细说明一般的主题,何时以及何时不使用它?

编辑:我知道这个主题有这本书,如果其他读者感兴趣的话;它可能包含我的答案,但我还没有机会通读它。

Pet*_*des 5

在现代 C++ 中,通常根本不应该使用std::memory_order_consumeor 。[[carries_dependency]]当委员会提出编译器可以实际实现的更好机制时,它们基本上已被弃用。

希望这不需要洒遍[[carries_dependency]]各处kill_dependency

2016-06 P0371R1:暂时阻止 memory_order_consume

人们普遍认为标准中当前的 memory_order_consume 定义没有用。当前所有编译器本质上都将其映射到 memory_order_acquire。困难似乎源于高实现复杂性,源于当前定义使用相当一般的“依赖关系”定义,因此需要频繁且不方便地使用kill_dependency调用,以及频繁需要[[carries_dependency] ] 注释。详细信息可参见P0098R0等。

值得注意的是,在 C++ 中x - x仍然带有依赖性,但大多数编译器自然会打破依赖性并用常量替换该表达式0。但如果编译器能够证明分支后的值范围的某些信息,编译器有时也会将数据依赖关系转换为控制依赖关系。


在刚刚升级的现代编译器上mo_consumemo_acquire完全积极的优化总是可能发生[[carries_dependency]]即使在使用 的kill_dependency代码中也永远不会获得任何好处,更不用说在其他代码中了。 mo_consume


mo_acquire对于 POWER 和 ARM 等弱有序 ISA 上的 RCU 等实际用例,这种增强可能会带来潜在的显着性能成本(额外的障碍)。请观看 Paul E. McKenney 的 CppCon 2015 演讲C++ Atomics:内存_order_consume 的悲伤故事的视频。(链接包含摘要)。

如果您想要真正的依赖性排序只读性能,您必须“自己动手”,例如通过使用mo_relaxed和检查 asm 来验证它是否已编译为具有依赖性的 asm。(避免用这样的值做任何“奇怪”的事情,比如跨函数传递它。)DEC Alpha 基本上已经死了,所有其他 ISA 都在 asm 中提供无障碍的依赖排序,只要 asm 本身具有数据依赖性

如果您不想自己动手并生活在危险之中,那么继续mo_consume在它应该能够工作的“简单”用例中使用可能不会有什么坏处;也许未来的某些mo_consume实现将具有相同的名称并以与 C++11 兼容的方式工作。


目前正在进行制作新的工作consume,例如 2018 年的http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0750r1.html


cur*_*guy 3

因为我读过的答案听起来好像你可以在任何地方粘贴这个代码,并且神奇地你会得到相同或更快的代码

获得更快代码的唯一方法是该注释允许省略栅栏。

所以它可能有用的唯一情况是:

  • 您的程序在重要的频繁执行的代码中对原子加载操作使用消耗排序;
  • “消耗值”不仅可以立即在本地使用,还可以传递给其他函数;
  • 目标CPU为消耗操作提供特定的保证(与该操作之前的给定栅栏一样强,仅针对该操作);
  • 编译器编写者认真对待他们的工作:他们设法将某个值的高级语言消耗转换为 CPU 级别消耗,以从 CPU 保证中获益。

这是获得明显更快的代码的一系列必要条件。

(C++ 社区的最新趋势是放弃发明一种在所有情况下都安全的正确编译方案,并为用户提供一种完全不同的方式来指示编译器生成“消耗”值的代码,其中有很多更明确、可简单翻译的 C++ 代码。)

一条评论说代码可以相同或更慢,但发布者没有详细说明。

当然,您可以在程序中随意添加的注释通常无法使代码更加高效!那太容易了,而且也是自相矛盾的。

要么某个注释指定了对代码的约束,即对编译器的承诺,并且您不能将其放在与代码中的保证不对应的任何地方(例如noexcept在 C++ 中,restrict在 C 中),否则它会破坏以各种方式编写代码(noexcept函数中的异常会停止程序,受限指针的别名可能会导致有趣的错误编译和不良行为(以前在这种情况下未定义行为);然后编译器可以使用它以特定方式优化代码。

或者该注释不会以任何方式限制代码,并且编译器不能依赖任何东西,并且该注释不会创造任何更多的优化机会

如果您在某些情况下获得了更高效的代码,而无需使用注释破坏程序,那么在其他情况下您可能会获得效率较低的代码。一般来说,这是正确的,对于消费语义来说尤其如此,它对 C++ 结构的翻译施加了前面描述的约束。

我想使用它的合适位置是在任何函数返回或参数上,这些函数返回或参数是指针或引用,并且将在调用线程内传递或返回

不,它可能有用的唯一一种情况是预期的调用函数可能会使用消耗内存顺序