繁体   English   中英

为什么 [[carries_dependency]] 不是 C++ 中的默认值?

[英]Why isn't [[carries_dependency]] the default in C++?

我知道memory_order_consume已被弃用,但我试图了解原始设计中的逻辑以及[[carries_dependency]]kill_dependency应该如何工作。 为此,我想要一个特定的代码示例,它会在 IBM PowerPC 或 DEC alpha 或什至具有假设编译器的假设架构上中断,该编译器在 C++11 或 C++14 中完全实现了消费语义。

我能想到的最好的例子是这样的:

int v;
std::atomic<int*> ap;

void
thread_1()
{
  v = 1;
  ap.store(&v, std::memory_order_release);
}

int
f(int *p [[carries_dependency]])
{
  return v;
}

void
thread_2()
{
  int *p;
  while (!(p = ap.load(std::memory_order_consume)))
    ;
  int v2 = f(p);
  assert(*p == v2);
}

我了解此代码中的断言可能会失败。 但是,如果从f中删除[[carries_dependency]] ,断言应该失败吗? 如果是这样,为什么会这样? 毕竟,您请求了memory_order_consume ,那么您为什么希望其他对v的访问能够反映获取语义呢? 如果删除[[carries_dependency]]不会使代码正确,那么[[carries_dependency]] (或使[[carries_dependency]]成为所有变量的默认值)破坏其他正确代码的示例是什么?

我唯一能想到的是,这可能与寄存器溢出有关? 如果 function 将寄存器溢出到堆栈上,然后重新加载它,这可能会破坏依赖链。 So maybe [[carries_dependency]] makes things efficient in some cases (says no need to issue memory barrier in the caller before calling this function) but also requires the callee to issue a memory barrier before any register spills or calling another function, which could在其他情况下效率会降低吗? 不过,我在这里抓住了稻草,所以仍然很想听听懂这些东西的人的来信……

return vint *p没有数据依赖性,因此您需要acquireconsume ap.load(consume) / f(p)才能与发布存储同步。

如果您使用了return *p那么这将是足够的,这要归功于依赖排序,因为该加载将对较早的加载具有数据依赖关系,CPU 无法更早地生成地址,因此在加载 from 之前从v加载ap看到你正在等待的价值。

有效地促进删除依赖排序的东西需要在 function 调用之前使用 memory 屏障来促进consume acquire

DEC Alpha 即使consume工作也总是需要一个屏障,即它必须促进consume才能acquire ,因为 ISA 不保证硬件的依赖排序。

一些 ISA(大多数只是 x86)的排序非常强,它们不需要屏障,因为每个负载都是获取负载,而不是与其他负载重新排序。 或者至少给人一种没有被重新排序的错觉; 实际实现推测性地提前加载,但如果检测到错误推测,即在架构上允许加载发生时缓存行仍然无效的情况下,会破坏管道。

所以 x86 和 Alpha 可能仍然可以使用[[carries-dependency]]版本,因为它们要么太强要么太弱, mo_consume不是硬件可以实际做的事情(比mo_acquire更便宜)。

对于 Alpha,这将取决于编译器将障碍放在哪里; 它可以将它放在f(p)return v之后,仅在实际取决于消耗负载的*p之前。 或者它可以像编译器现在所做的那样,在加载时促进消耗在现场获取(因为在证明其当前设计难以支持后,消耗已被弃用。)


至于为什么 ISO C++11 决定在通过 function 边界时促进consume结果有效acquire ,这可能是一个可用性考虑。 但也有表现。 否则,编译器将失去对传入的 function 参数进行一些优化的能力。

例如int ready = foo.load(consume); / if(ready == 1) return non_atomic[ready-ready]; 需要生成对消耗加载结果具有数据依赖性的 asm,这与编译器仅优化它以return *non_atomic时不同。

(您可能熟悉 x86 xor eax,eax是一种将寄存器归零的好方法。在保证依赖排序的弱排序 ISA 中,例如 ARM, eor r0, r0,r0保证不会破坏对旧值的依赖r0的。)

if(ready == 1)这样的分支中的常量传播也应该是可能的; 里面的代码if只能在ready具有常量值1时运行。 所以即使我们不取消它, non_atomic[ready]也不能优化为non_atomic[1]; .

如果每个传入的 function arg 都可能带有依赖项,则编译器将无法对任何 function args 或从它们派生的值进行那些通常的优化。


相关回复: consume是关于和/或其弃用的,并且它仍然以手动方式使用,在 Linux kernel 代码(例如 RCU)中具有volatile

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM