有人能用凡人都能理解的语言来解释吗?
2 回答
[[carries_dependency]]
用于允许跨函数调用携带依赖项。std::memory_order_consume
这可能允许编译器在用于在具有弱排序架构(如 IBM 的 POWER 架构)的平台上的线程之间传输值时生成更好的代码。
特别是,如果将读取的值memory_order_consume
传递给函数,然后不传递[[carries_dependency]]
,则编译器可能必须发出内存栅栏指令以确保支持适当的内存排序语义。如果参数带有注释,[[carries_dependency]]
那么编译器可以假设函数体将正确地携带依赖关系,并且这个栅栏可能不再需要。
类似地,如果一个函数返回一个用 加载的值memory_order_consume
,或者从这个值派生的值,那么在没有[[carries_dependency]]
编译器的情况下,可能需要插入一个栅栏指令来保证支持适当的内存排序语义。使用[[carries_dependency]]
注释,这个栅栏可能不再需要,因为调用者现在负责维护依赖关系树。
例如
void print(int * val)
{
std::cout<<*val<<std::endl;
}
void print2(int * [[carries_dependency]] val)
{
std::cout<<*val<<std::endl;
}
std::atomic<int*> p;
int* local=p.load(std::memory_order_consume);
if(local)
std::cout<<*local<<std::endl; // 1
if(local)
print(local); // 2
if(local)
print2(local); // 3
在第 (1) 行中,依赖项是显式的,因此编译器知道它local
已取消引用,并且它必须确保保留依赖项链以避免在 POWER 上出现栅栏。
在第 (2) 行中, 的定义print
是不透明的(假设它不是内联的),因此编译器必须发出栅栏以确保读*p
入print
返回正确的值。
在第 (3) 行,编译器可以假设虽然print2
也是不透明的,但从参数到取消引用值的依赖关系保留在指令流中,并且在 POWER 上不需要围栏。显然, 的定义print2
实际上必须保留这种依赖关系,因此该属性也会影响为print2
.
简而言之,我认为,如果有 Carry_dependency 属性,则应该针对某个情况优化为函数生成的代码,此时实际参数将真正来自另一个线程并带有依赖项。对于返回值也是如此。如果该假设不成立(例如在单线程程序中),则可能会缺乏性能。但也没有 [[carries_dependency]] 可能会导致相反情况下的糟糕表现......除了性能改变之外不会发生其他影响。
例如,指针解引用操作取决于指针先前是如何获得的,如果指针 p 的值来自另一个线程(通过“消费”操作),则考虑该另一个线程先前分配给 *p 的值并且可见。可能有另一个指针 q 等于 p (q==p),但由于它的值不是来自另一个线程,所以 *q 的值可能与 *p 不同。实际上 *q 可能会引发一种“未定义的行为”(因为访问内存位置与另一个进行分配的线程不协调)。
真的,在某些工程案例中,内存(和思维)的功能似乎存在一些大错误.... > :-)