[[carries_dependency]]属性是什么意思?


75

有人可以用凡人都能理解的语言来解释它吗?



1
@DumbCoder:谢谢,这绝对比N2390本身好,不幸的是,它重定向到许多其他“需要理解该建议的论文”……似乎我的问题过于广泛了:)
Yakov Galka

1
在正常语言中,它是一个可选的优化提示(当前未由每个编译器实现或忽略)在理论上可以允许编译器在很少修改且共享频繁读取的数据时生成稍微更好的多线程代码。干得好,措辞如此扭曲,以至于没有人会使用它:-)
Damon

我还指出,你对这个问题,其中一个很好的书由安东尼·威廉姆斯中提到:stackoverflow.com/questions/4938258/...
五花八门的

1
@Damon措辞不是歪曲,而是语义完全愚蠢:d?a:b打破了依赖关系,但d->static_fun()没有……那是没有道理的。而且它不允许“稍微更好的多线程代码”,在某些处理器上,避免频繁操作的围栏会更好。“当很少修改时,共享经常读取的数据”只要有指向它的指针,消费也适用于经常修改的数据,并且记录一旦发布就只能读取,这是常态。
curiousguy19年

Answers:


57

[[carries_dependency]]用于允许依赖关系在函数调用之间传递。当与std::memory_order_consume诸如IBM POWER架构之类的顺序较弱的平台上的线程之间传输值一起使用时,这可能会使编译器生成更好的代码。

特别是,如果将使用读取的值memory_order_consume传递给函数,则如果不带[[carries_dependency]],则编译器可能必须发出内存围栅指令以确保维持适当的内存排序语义。如果给参数加上注释,[[carries_dependency]]则编译器可以假定函数主体将正确地承载依赖项,并且可能不再需要此防护。

类似地,如果一个函数返回一个加载有memory_order_consume或从该值派生的值,则[[carries_dependency]]可能不需要编译器插入fence指令即可保证适当的内存排序语义得以保留。有了[[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是不透明的(假定它没有内联),因此编译器必须发出篱笆,以确保*pin读取print返回正确的值。

在第(3)行上,编译器可以假定,尽管print2它也是不透明的,但从参数到取消引用的值的依赖关系仍保留在指令流中,并且在POWER上不需要隔离。显然,的定义print2实际上必须保留此依赖关系,因此该属性还将影响为生成的代码print2


18
这是一个很好的答案。但是...您将如何对函数进行编码以保留依赖性?编码不正确的函数会是什么样子,后果是什么?
2011年

2
顺便说一句,我以PDF的形式收到了您的图书的预发行本。这是一本很棒的书。我真的希望您一直保持“隔壁的人正在接电话”的比喻。那是了解正在发生的事情的好工具。
2011年

2
从源的POV中,您需要做的就是使用该[[carries_dependency]]属性,std::kill_dependency除非您是故意的,否则不要调用它。然后,编译器将确保它不会破坏所生成代码中的依赖关系链。
Anthony Williams

8
@AnthonyWilliams:我在这里使用Omnifarious:听起来像您只需要粘贴所有函数声明,[[carries_dependency]]编译器就会神奇地生成更快的代码。我会对无法使用[[carries_dependency]]或必须使用的示例函数感兴趣std::kill_dpendency
Marc Mutz-mmutz

2
@ MarcMutz-mmutz“编译器将神奇地生成更快的代码”错误。编译器将生成相等或更少优化(较慢)的代码。
curiousguy 2012年

-2

简而言之,我认为,如果有carry_dependency属性,则应针对一种情况优化函数生成的代码,此时实际参数确实来自另一个线程并带有依赖项。对于返回值也是如此。如果该假设不成立,则可能会缺乏性能(例如,在单线程程序中)。但是,如果缺少[[carries_dependency]],则在相反的情况下可能会导致性能下降。

例如,指针取消引用操作取决于先前获取指针的方式,并且如果指针p的值来自另一个线程(通过“消耗”操作),则应考虑该另一个线程先前为* p分配的值。并且可见。可能存在另一个等于p(q == p)的指针q,但是由于其值不是来自其他线程,因此* q的值可能与* p不同。实际上* q可能会引起某种“未定义的行为”(因为访问内存位置与进行分配的另一个线程不协调)。

确实,在某些工程案例中,内存(和思维)的功能似乎存在一些大错误。...::-)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.