[[carries_dependency]]的含义以及实现方法 [英] [[carries_dependency]] what it means and how to implement

查看:72
本文介绍了[[carries_dependency]]的含义以及实现方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 SO 发布.

但是我无法理解的是接受的答案中的以下句子:

But what I could not understand is the below sentences in the accepted answer :

"特别是如果传入了使用memory_order_consume读取的值到一个函数,然后没有[[carries_dependency]],然后是编译器可能必须发出一条内存围栏指令,以确保保留适当的内存排序语义.如果参数是用[[carries_dependency]]注释,然后编译器可以假定函数主体将正确地携带依赖关系,并且这栅栏可能不再是必需的.

"In particular, if a value read with memory_order_consume is passed in to a function, then without [[carries_dependency]], then the compiler may have to issue a memory fence instruction to guarantee that the appropriate memory ordering semantics are upheld. If the parameter is annotated with [[carries_dependency]] then the compiler can assume that the function body will correctly carry the dependency, and this fence may no longer be necessary.

类似地,如果函数返回一个加载有memory_order_consume,或从这样的值派生,然后不[[carries_dependency]]可能需要编译器插入篱笆指令以确保适当的内存顺序语义得到维护.使用[[carries_dependency]]批注,此围墙可能不再是必需的,因为呼叫者现在负责维护依存关系树."

Similarly, if a function returns a value loaded with memory_order_consume, or derived from such a value, then without [[carries_dependency]] the compiler may be required to insert a fence instruction to guarantee that the appropriate memory ordering semantics are upheld. With the [[carries_dependency]] annotation, this fence may no longer be necessary, as the caller is now responsible for maintaining the dependency tree."

让我们逐步进行:

"如果将使用memory_order_consume读取的值传递给函数,然后如果没有[[carries_dependency]],则编译器可能不得不发出内存围栏指令,以确保适当的内存排序语义得以保留."

"if a value read with memory_order_consume is passed in to a function, then without [[carries_dependency]], then the compiler may have to issue a memory fence instruction to guarantee that the appropriate memory ordering semantics are upheld."

因此,对于释放消费型内存模型中的原子变量,当将原子变量作为参数传递给函数时,编译器将引入fence硬件指令,以便它始终具有提供给原子变量的最新和更新值.功能.

So for an atomic variable in release-consume memory model when atomic variable is being passed as a parameter to the function the compiler will introduce a fence hardware instruction so that it always has the latest and updated value of the atomic variable provided to the function.

下一步-

"如果参数是用[[carries_dependency]]注释的,则编译器可以假定函数主体将正确携带依赖性,并且可能不再需要此防护."

"If the parameter is annotated with [[carries_dependency]] then the compiler can assume that the function body will correctly carry the dependency, and this fence may no longer be necessary."

这让我感到困惑-原子变量值已经被消耗了,那么函数的依赖关系又是什么呢?

This is confusing me - the atomic variable value is already consumed and then what dependency the function is carried?

类似地-

",如果函数返回装载有memory_order_consume的值,或者从这样的值派生,然后没有[[carries_dependency]]可能要求编译器插入fence指令以确保保持适当的内存排序语义.随着[[carries_dependency]]批注,此围栏可能不再是必要,因为呼叫者现在负责维护依赖关系树."

"if a function returns a value loaded with memory_order_consume, or derived from such a value, then without [[carries_dependency]] the compiler may be required to insert a fence instruction to guarantee that the appropriate memory ordering semantics are upheld. With the [[carries_dependency]] annotation, this fence may no longer be necessary, as the caller is now responsible for maintaining the dependency tree."

从该示例中,尚不清楚它要说明的关于携带依赖项的要点是什么?

From the example its not clear what the point it is trying to state about carrying the dependency?

推荐答案

仅供参考, memory_order_consume (和 [[carries_dependency]] )在本质上已被弃用,因为它太难了使编译器可以按照C ++ 11设计它们的方式高效,正确地实施规则.(和/或因为在整个地方最终都需要 [[carries_dependency]] 和/或 kill_dependency .)请参见

Just FYI, memory_order_consume (and [[carries_dependency]]) is essentially deprecated because it's too hard for compilers to efficiently and correctly implement the rules the way C++11 designed them. (And/or because [[carries_dependency]] and/or kill_dependency would end up being needed all over the place.) See P0371R1: Temporarily discourage memory_order_consume.

当前的编译器只是将 mo_consume 视为 mo_acquire (因此,在需要使用ISA的ISA上,在消耗负载之后设置一个障碍).如果要执行无障碍数据依赖排序,则必须使用 mo_relaxed 欺骗编译器,并仔细编码,以免可能导致编译器在没有实际依赖的情况下创建asm.(例如Linux RCU).参见 C ++ 11:memory_order_relaxed之间的区别和memory_order_consume 以获得更多详细信息和链接,以及 mo_consume 旨在公开的asm功能.

Current compilers simply treat mo_consume as mo_acquire (and thus on ISAs that need one, put a barrier right after the consume load). If you want the performance of data dependency ordering without barriers, you have to trick the compiler by using mo_relaxed and code carefully to avoid things that would make it likely for the compiler to create asm without an actual dependency. (e.g. Linux RCU). See C++11: the difference between memory_order_relaxed and memory_order_consume for more details and links about that, and the asm feature that mo_consume was designed to expose.

C11中的内存订单消耗使用情况.
了解依赖顺序的概念(在asm中)对于理解此C ++功能的设计基本上至关重要.

当将[an]原子变量作为参数传递给函数时,编译器将引入围栏硬件指令...

When [an] atomic variable is being passed as a parameter to the function the compiler will introduce a fence hardware instruction ...

您不会传递原子变量",首先是功能那甚至意味着什么?如果要传递指向原子对象的指针或引用,则该函数将对其进行自身的加载,并且该函数的源代码将使用或不使用 memory_order_consume .

You don't "pass an atomic variable" to a function in the first place; what would that even mean? If you were passing a pointer or reference to an atomic object, the function would be doing its own load from it, and the source code for that function would use memory_order_consume or not.

相关的事情是使用mo_consume从原子变量传递值 loaded .像这样:

The relevant thing is passing a value loaded from an atomic variable with mo_consume. Like this:

    int tmp = shared_var.load(std::memory_order_consume);
    func(tmp);

func 可以使用该arg作为 atomic< int> 数组的索引来进行 mo_relaxed 加载.为了即使在没有内存屏障的情况下也可以在 shared_var.load 之后对该加载进行依赖排序, func 的代码生成必须确保该加载具有对asm数据的依赖arg,即使C ++代码做了类似 tmp-= tmp; 之类的事情,编译器通常也只会将其视为 tmp = 0; (杀死先前的值).

func may use that arg as an index into an array of atomic<int> to do an mo_relaxed load. For that load to be dependency-ordered after the shared_var.load even without a memory barrier, code-gen for func has to make sure that load has an asm data dependency on the arg, even if the C++ code does something like tmp -= tmp; that compilers would normally just treat the same as tmp = 0; (killing the previous value).

但是 [[carries_dependency]] 将使编译器在实现诸如 array [idx + tmp] 之类的内容时仍引用具有数据依赖性的零值.

But [[carries_dependency]] would make the compiler still reference that zeroed value with a data dependency in implementing something like array[idx+tmp].

原子变量值已经被消耗了,那么函数的依赖关系又是什么呢?

the atomic variable value is already consumed and then what dependency the function is carried?

已消耗"这不是一个有效的概念. consum 而不是 acquire 的全部要点是,以后的加载顺序正确,因为它们对 mo_consume 具有 data 依赖关系代码>加载结果,让您避免障碍.如果要在原始加载之后进行排序,则以后的每个加载都需要这样的依赖关系.您无法说一个值是已经消耗".

"Already consumed" is not a valid concept. The whole point of consume instead of acquire is that later loads are ordered correctly because they have a data dependency on the mo_consume load result, letting you avoid barriers. Every later load needs such a dependency if you want it ordered after the original load; there is no sense in which you can say a value is "already consumed".

如果由于对一个函数缺少carry_dependency而最终插入了障碍以促进消费获取,则后面的函数将不需要另一个障碍,因为您可以说该值是已被获取".(尽管这不是标准术语.您应该在加载后订购第一个障碍后说代码.)

If you do end up inserting a barrier to promote consume to acquire because of a missing carries_dependency on one function, later functions wouldn't need another barrier because you could say the value was "already acquired". (Although that's not standard terminology. You'd instead say code after the first barrier was ordered after the load.)

了解Linux内核如何处理此问题,以及它们的手动操作原子和所支持的有限编译器集,可能会很有用.搜索依赖关系".在 https://github.com/torvalds/linux/blob/master/Documentation/memory-barriers.txt ,并注意控件依赖项"与控件依赖项"之间的区别例如 if(flag)data.load()与数据依赖项,例如 data [idx] .load .

It might be useful to understand how the Linux kernel handles this, with their hand-rolled atomics and limited set of compilers they support. Search for "dependency" in https://github.com/torvalds/linux/blob/master/Documentation/memory-barriers.txt, and note the difference between a "control dependency" like if(flag) data.load() vs. a data dependency like data[idx].load.

IIRC,即使C ++不能保证 mo_consume 依赖关系排序,例如 if(x.load(consume))tmp = y.load(); .

IIRC, even C++ doesn't guarantee mo_consume dependency ordering when the dependency is a conditional like if(x.load(consume)) tmp=y.load();.

请注意,如果例如只有两个可能的值,则编译器 有时会将数据依赖项转换为控件依赖项.这将破坏 mo_consume ,并且如果值来自 mo_consume 加载或 [[carries_dependency]] 函数arg.这就是为什么很难实现的部分原因.这将需要教授大量有关数据依存关系排序的优化途径,而不是仅仅期望用户编写不会执行通常会优化掉的事情的代码.(就像 tmp-= tmp; )

Note that compilers will sometimes turn a data dependency into a control dependency if there's only 2 possible values for example. This would break mo_consume, and be an optimization that wouldn't be allowed if the value came from a mo_consume load or a [[carries_dependency]] function arg. This is part of why it's hard to implement; it would require teaching lots of optimization passes about data dependency ordering instead of just expecting users to write code that doesn't do things which will normally optimize away. (Like tmp -= tmp;)

这篇关于[[carries_dependency]]的含义以及实现方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆