`std :: kill_dependency`做了什么,为什么我要使用它?

R. *_*des 57 c++ multithreading memory-model c++11

我一直在阅读有关新C++ 11内存模型的内容,并且我已经开始使用该std::kill_dependency功能(§29.3/ 14-15).我很难理解为什么我会想要使用它.

我在N2664提案中找到了一个例子,但它并没有多大帮助.

它首先显示代码而不是std::kill_dependency.这里,第一行将依赖性携带到第二行中,第二行将依赖性携带到索引操作中,然后将依赖性携带到do_something_with函数中.

r1 = x.load(memory_order_consume);
r2 = r1->index;
do_something_with(a[r2]);
Run Code Online (Sandbox Code Playgroud)

还有一个例子std::kill_dependency用于打破第二行和索引之间的依赖关系.

r1 = x.load(memory_order_consume);
r2 = r1->index;
do_something_with(a[std::kill_dependency(r2)]);
Run Code Online (Sandbox Code Playgroud)

据我所知,这意味着索引和调用do_something_with不是在第二行之前排序的依赖项.根据N2664:

这允许编译器将调用重新排序do_something_with,例如,通过执行预测值的推测优化a[r2].

为了使得需要调用do_something_with该值a[r2].假设编译器"知道"数组填充了零,它可以根据需要优化该调用do_something_with(0);并相对于其他两个指令重新排序此调用.它可以产生以下任何一种:

// 1
r1 = x.load(memory_order_consume);
r2 = r1->index;
do_something_with(0);
// 2
r1 = x.load(memory_order_consume);
do_something_with(0);
r2 = r1->index;
// 3
do_something_with(0);
r1 = x.load(memory_order_consume);
r2 = r1->index;
Run Code Online (Sandbox Code Playgroud)

我的理解是否正确?

如果do_something_with通过其他方式与另一个线程同步,这对于x.load调用的顺序和另一个线程意味着什么?

假设我的描述是正确的,那还有一件事让我感到困惑:当我编写代码时,是什么原因导致我选择杀死依赖?

bdo*_*lan 39

memory_order_consume的目的是确保编译器不会执行某些可能会破坏无锁算法的不幸优化.例如,考虑以下代码:

int t;
volatile int a, b;

t = *x;
a = t;
b = t;
Run Code Online (Sandbox Code Playgroud)

符合标准的编译器可以将其转换为:

a = *x;
b = *x;
Run Code Online (Sandbox Code Playgroud)

因此,a可能不等于b.它也可以:

t2 = *x;
// use t2 somewhere
// later
t = *x;
a = t2;
b = t;
Run Code Online (Sandbox Code Playgroud)

通过使用load(memory_order_consume),我们要求在使用点之前不要移动正在加载的值的使用.换一种说法,

t = x.load(memory_order_consume);
a = t;
b = t;
assert(a == b); // always true
Run Code Online (Sandbox Code Playgroud)

标准文档考虑了您可能只对订购结构的某些字段感兴趣的情况.例子是:

r1 = x.load(memory_order_consume);
r2 = r1->index;
do_something_with(a[std::kill_dependency(r2)]);
Run Code Online (Sandbox Code Playgroud)

这指示编译器允许它有效地执行此操作:

predicted_r2 = x->index; // unordered load
r1 = x; // ordered load
r2 = r1->index;
do_something_with(a[predicted_r2]); // may be faster than waiting for r2's value to be available
Run Code Online (Sandbox Code Playgroud)

甚至这个:

predicted_r2 = x->index; // unordered load
predicted_a  = a[predicted_r2]; // get the CPU loading it early on
r1 = x; // ordered load
r2 = r1->index; // ordered load
do_something_with(predicted_a);
Run Code Online (Sandbox Code Playgroud)

如果编译器知道do_something_with不会改变r1或r2的加载结果,那么它甚至可以将它一直提升:

do_something_with(a[x->index]); // completely unordered
r1 = x; // ordered
r2 = r1->index; // ordered
Run Code Online (Sandbox Code Playgroud)

这使编译器在优化方面有了更多的自由度.

  • @curiousguy,不!编译器无法将存储优化到volatile变量中,但是它可以做什么_它想要什么_来自`t`的负载 (6认同)
  • 差不多,虽然我已经指出,当你使用无锁算法时,如果你采取了一个错误的步骤,你已经在处理微妙的错误 (5认同)
  • @curiousguy,确实如此,而且这里也不相关 - 我的示例都没有从 易失性分配到易失性。这里使用 挥发性只是为了简化示例 - 如果“a”和“b”不是挥发性的,编译器可以做一些甚至更肮脏的伎俩。 (2认同)

Cor*_*ica 10

除了另一个答案之外,我还要指出,C++社区最权威的领导者之一Scott Meyers对memory_order_consume非常强烈.他基本上说他相信它在标准中没有位置.他说有两种情况,memory_order_consume有任何影响:

  • 异构体系结构旨在支持1024+核心共享内存机器.
  • DEC Alpha

是的,再次,DEC Alpha通过使用在任何其他芯片中看不到的优化,直到多年后在荒谬的专业机器上找到它的方式.

特别的优化是那些处理器允许在实际获得该字段的地址之前取消引用字段(即,它甚至在使用x的预测值查找x之前查找x-> y).然后它返回并确定x是否是它预期的值.成功后,它节省了时间.失败时,它必须返回并再次获得x-> y.

Memory_order_consume告诉编译器/体系结构这些操作必须按顺序发生.但是,在最有用的情况下,最终会想要做(x-> yz),其中z不会改变.memory_order_consume将强制编译器按顺序保持xy和z.kill_dependency(x-> y).z告诉编译器/架构它可能会继续进行这种恶意的重新排序.

99.999%的开发人员可能永远不会在需要此功能的平台上工作(或者根本没有任何影响).