什么`std :: kill_dependency`做,为什么我想要使用它?
我一直在阅读有关新的C ++ 11内存模型,我已经遇到了std::kill_dependency
函数(§29.3/ 14-15)。 我正在努力理解为什么我会想要使用它。
我在N2664提案中find了一个例子,但没有多大帮助。
它首先显示没有std::kill_dependency
代码。 这里,第一行在第二行中携带一个依赖项,它依赖于索引操作,然后将依赖项携带到do_something_with
函数中。
r1 = x.load(memory_order_consume); r2 = r1->index; do_something_with(a[r2]);
还有一个例子使用std::kill_dependency
来打破第二行和索引之间的依赖关系。
r1 = x.load(memory_order_consume); r2 = r1->index; do_something_with(a[std::kill_dependency(r2)]);
据我所知,这意味着索引和对do_something_with
的调用在第二行之前是不依赖的。 根据N2664:
这允许编译器将调用重新sorting为
do_something_with
,例如,通过执行预测优化来预测a[r2]
的值。
为了调用do_something_with
的值a[r2]
是需要的。 如果假设编译器“知道”数组填充了零,则可以优化对do_something_with(0);
调用do_something_with(0);
并根据其他两个指令重新sorting此通话。 它可以产生以下任何一种:
// 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;
我的理解是正确的吗?
如果do_something_with
通过其他方式与另一个线程同步,这对于x.load
调用和这个其他线程的sorting意味着什么?
假设我的代码是正确的,那么还有一件事情会让我感到困惑:当我编写代码时,有什么原因会导致我select杀死一个依赖项?
memory_order_consume的目的是为了确保编译器不会做一些不幸的优化,可能会破坏无锁algorithm。 例如,考虑这个代码:
int t; volatile int a, b; t = *x; a = t; b = t;
符合的编译器可以将其转换为:
a = *x; b = *x;
因此,a可能不等于b。 它也可能:
t2 = *x; // use t2 somewhere // later t = *x; a = t2; b = t;
通过使用load(memory_order_consume)
,我们要求在使用之前不要使用被加载的值。 换一种说法,
t = x.load(memory_order_consume); a = t; b = t; assert(a == b); // always true
标准文件考虑的情况下,您可能只有兴趣订购结构的某些领域。 例子是:
r1 = x.load(memory_order_consume); r2 = r1->index; do_something_with(a[std::kill_dependency(r2)]);
这指示编译器允许它有效地执行此操作:
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
甚至这个:
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);
如果编译器知道do_something_with
不会改变r1或r2的加载结果,那么它甚至可以一直提升:
do_something_with(a[x->index]); // completely unordered r1 = x; // ordered r2 = r1->index; // ordered
这可以让编译器在优化方面多一点自由。
除了另一个答案之外,我还要指出,C ++社区权威领导人之一的Scott Meyers强烈地抨击了memory_order_consume。 他基本上说他认为这个标准没有地位。 他说有两种情况,memory_order_consume有任何作用:
- devise用于支持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告诉编译器/架构它可能会继续执行这种恶意的重新sorting。
99.999%的开发人员可能永远不会在需要该function的平台上工作(或者完全没有任何效果)。
kill_dependency
的常见用例来自以下内容。 假设你想对一个不平凡的共享数据结构进行primefaces更新。 一个典型的方法是非原生地创build一些新的数据,并且从数据结构primefaces地摆动指针到新的数据。 一旦你这样做了,你就不会改变新的数据,直到你把指针从它移开到别的东西(并等待所有的读者腾空)。 这个范例被广泛使用,例如Linux内核中的读取 – 拷贝更新。
现在,假设读者读取指针,读取新的数据,然后再回来读取指针,发现指针没有改变。 硬件不能指出指针还没有被更新,所以通过consume
语义,他不能使用数据的caching副本,但必须从内存中读取它。 (换句话说,硬件和编译器不能在读指针之前推测性地移动数据的读取。)
这是kill_dependency
来救援的地方。 通过将指针包装在kill_dependency
,可以创build一个不再传播依赖关系的值,允许通过指针访问来使用新数据的caching副本。
我的猜测是,它使这个优化。
r1 = x.load(memory_order_consume); do_something_with(a[r1->index]);