Ant*_*ams 53

[[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<<*p<<std::endl;
}

void print2(int * [[carries_dependency]] val)
{
    std::cout<<*p<<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
Run Code Online (Sandbox Code Playgroud)

在第(1)行中,依赖是显式的,因此编译器知道它local被解除引用,并且它必须确保保留依赖关系链以避免POWER上的栅栏.

在第(2)行中,定义print是不透明的(假设它没有内联),因此编译器必须发出一个fence,以确保读*pprint返回正确的值.

在第(3)行,编译器可以假设虽然print2也是不透明的,但是从参数到解除引用的值的依赖性保留在指令流中,并且在POWER上不需要栅栏.显然,定义print2必须实际保留这种依赖关系,因此该属性也会影响生成的代码print2.

  • 这是一个很好的答案.但是......你将如何编写函数以保持依赖?不正确编码的函数会是什么样的,后果会是什么? (16认同)
  • @AnthonyWilliams:我在这里与Omnifarious:听起来你只需要用`[[carry_dependency]]`来填充所有的函数声明,编译器会神奇地生成更快的代码.我会对一个示例函数感兴趣,你不能*使用`[[carry_dependency]]`或者你必须使用`std :: kill_dpendency`. (8认同)
  • 顺便说一下,我以PDF格式发布了你的预发行版本.这是一本很棒的书.我真的希望你一直把你的'人在一个小房间里接到电话'的比喻.这是了解正在发生的事情的一个很好的工具. (2认同)
  • 从源的POV,您需要做的就是使用`[[carry_dependency]]`属性,而不是调用`std :: kill_dependency`,除非你的意思.然后,编译器将确保它不会破坏生成的代码中的依赖关系链. (2认同)
  • @ MarcMutz-mmutz"_编译器将神奇地生成更快的代码_"错误.编译器将生成相等或较不优化(较慢)的代码. (2认同)
  • @ MarcMutz-mmutz:的确,你忘记了单线程调用者调用`print2`的情况(实际上,技术上,任何不是通过消费链获得的参数); 维护依赖链现在是开销. (2认同)