问题描述
根据 C++ 标准:
如果 - A 的值用作 B 的操作数,则评估 A 携带对评估 B 的依赖,除非:
— B 是 std::kill_dependency (29.3) 的任何特化的调用,或
——A 是内置逻辑 AND(&&,参见 5.14)或逻辑 OR(||,参见 5.15)运算符的左操作数,或
——A 是条件 (?:,见 5.16) 运算符的左操作数,或
——A 是内置逗号 (,) 运算符 (5.18) 的左操作数; (...)
我可以理解为什么在调用 kill_dependency 时关系之前排序的依赖会停止,但为什么逻辑 AND、OR、逗号等运算符也会破坏依赖链?
这是否意味着下面的代码有未定义的行为?
//thread1
int y = 2
atomicVal.store(true);
//thread2
auto x = atomicVal.load(std::memory_order_consume);
cout << x && y;
解决方法
memory_order_consume
试图公开用于 C++ 的 asm 级 CPU 功能。 (它是 temporarily deprecated 直到它可以重新设计为编译器可以在实践中实现的东西,并且不需要在源代码中产生太多 kill_dependency
噪声)。理解 CPU 行为是理解旨在暴露它的 C++ 东西的设计的关键。
这完全是关于数据依赖,而不是像条件分支这样的控制依赖。 C++11: the difference between memory_order_relaxed and memory_order_consume 和 [[carries_dependency]] what it means and how to implement 有更多详细信息。
例如一条 add x2,x2,x3
指令在它的两个输入寄存器都准备好之前不能执行,并且 ldr w1,[x2]
在地址准备好之前都不能执行加载,所以如果 x2
来自另一个加载,它会自动在这之前订购。 (假设 CPU 硬件的设计不违反因果关系,例如通过进行价值预测或 DEC Alpha 在极少数情况下违反因果关系所做的任何事情)。但是 cbz w1,reg_was_zero
是可以预测的,因此让 reg_was_zero: ldr w3,[x4]
等待产生 w1 的负载是不够的。 (这是 AArch64 asm,顺便说一句,一种保证依赖顺序的弱排序 ISA。)
||
或 left && right
的短路评估在逻辑上与 if(left) right
相同,因此可以预期分支预测 + 推测执行会运行即使左侧尚未执行,右侧也是如此。 没有数据依赖,只有控制依赖。
显然逗号 left,right
并没有在双方之间建立任何联系,它基本上是一种将 left; right;
塞进单个表达式的方法。
当然,如果你在左右两边使用相同的变量,数据依赖可以这样存在,但它不是由操作符创建的。