[英]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 v
对int *p
没有数据依赖性,因此您需要acquire
不consume
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.