`atomic_compare_exchange_strong_explicit()` -- 当参数 success 和 failure 不相等时,各种组合有什么作用?
`atomic_compare_exchange_strong_explicit()` -- what do the various combinations of `success` and `failure` parameter do, when not equal?
atomic_compare_exchange_strong_explicit()
函数有两个 memory_order
参数,success
和 failure
(atomic_compare_exchange_weak_explicit()
也是如此)。取消选择 C11/C18 标准,我发现 success
和 failure
的允许值为:
success = memory_order_relaxed failure = memory_order_relaxed
success = _release failure = _relaxed
success = _consume failure = _relaxed
or _consume
success = _acquire failure = _relaxed
or _acq_rel or _consume (?)
or _acquire
success = _seq_cst failure = _relaxed
or _consume (?)
or _acquire
or _seq_cst
标准还说:
Further, if the comparison is true, memory is affected according to the value of
success, and if the comparison is false, memory is affected according to the value of
failure. These operations are atomic read-modify-write operations (5.1.2.4).
您的 ARM、POWER-PC 和其他 "LL/SC" 设备执行 Load-Link/Cmp/Store-Conditional 序列来实现 atomic-cmp-exchange,其中 Load-Link 可能是也可能不是 _acquire 并且存储条件可能是也可能不是 _release。
所以我可以理解:success = _acq_rel and failure = _acquire.
我无法理解的是(除其他外):成功 = _acq_rel 和失败 = _relaxed。当然,为了实现 _acq_rel,Load-Link 必须是 _acquire 吗?如果 cmp 失败,那么降级到 _relaxed 肯定为时已晚?
success
和 failure
参数组合的预期含义是什么(当它们不相等时)?
[我总是被标准迷惑了,而且 failure
内存顺序实际上可能只是 success
内存顺序的读取一半是。]
在某些 ISA 上的 asm 中实现获取负载的一种方法是普通负载 后跟 栅栏,例如在 ARMv8 引入 ldar
/ ldaxr
之前,在像 PowerPC 或 ARM 这样的 ISA 上。 如果失败排序不包含获取,则可以跳过后面的围栏。
一个 LL/SC CAS_weak 在没有真正的 ISA 的伪 asm 中可能看起来像这样:
ll r0, mem
cmp r0, r1
jne .fail # early-out compare fail
sc r2, mem # let's pretend this sets CF condition code on SC failure
jc .fail # jump if SC failed
lwsync # LoadLoad, StoreStore, and LoadStore but not StoreLoad
... CAS success path
.fail: # we jump here without having executed any barriers
... CAS failure path
这(我认为)可能是 mem.compare_exchange_weak(r1, r2, mo_acquire, mo_relaxed);
在某些机器上的有效实现。
这只是一个获取操作,因此整个 RMW 可以使用较早的操作重新排序(LL/SC 的性质使它们在全局顺序中粘在一起)。术前无门槛,术前无门槛。
lwsync
是一个 PowerPC 屏障指令,它阻止除 StoreLoad 之外的所有重新排序(即不刷新存储缓冲区)。 https://preshing.com/20120913/acquire-and-release-semantics/ and https://preshing.com/20120930/weak-vs-strong-memory-models/
为了在大多数 ISA 上实现 CAS(..., acq_rel, relaxed)
,我们还会 运行 在 LL/SC 之前 一个屏障(将它与之前的操作,创建发布部分)。这甚至会在失败路径上执行,但 不会 创建获取语义。它不会将负载与以后的操作分开。
AFAIK 如果比较失败,您不想在 LL 和 SC 之间设置围栏以便跳过。这将延长事务并使其有更多机会因其他线程的 activity 而失败。
与往常一样,在真正的 asm 之上实现 C++ 内存模型时,您会做一些必要的事情,但不会更强,因为底层 ISA 提供的原语有限制。 大多数 ISA 不会使 CAS(acq_rel,松弛)故障路径实际上像普通松弛负载一样便宜,要么是因为这是不可能的,要么是因为它会损害正常情况下的性能。但是在某些情况下,它仍然可以 更少 比失败方需要获取语义的代价要小。
一些 ISA(如 ARM)显然只有完整的屏障(dsb ish
),所以即使 acq_rel 最终也会耗尽存储缓冲区。 (因此 ARMv8 引入获取加载和顺序释放存储 非常 很好,因为它完美匹配 C++ seq-cst 语义并且可能比屏障便宜得多。)
atomic_compare_exchange_strong_explicit()
函数有两个 memory_order
参数,success
和 failure
(atomic_compare_exchange_weak_explicit()
也是如此)。取消选择 C11/C18 标准,我发现 success
和 failure
的允许值为:
success = memory_order_relaxed failure = memory_order_relaxed
success = _release failure = _relaxed
success = _consume failure = _relaxed
or _consume
success = _acquire failure = _relaxed
or _acq_rel or _consume (?)
or _acquire
success = _seq_cst failure = _relaxed
or _consume (?)
or _acquire
or _seq_cst
标准还说:
Further, if the comparison is true, memory is affected according to the value of success, and if the comparison is false, memory is affected according to the value of failure. These operations are atomic read-modify-write operations (5.1.2.4).
您的 ARM、POWER-PC 和其他 "LL/SC" 设备执行 Load-Link/Cmp/Store-Conditional 序列来实现 atomic-cmp-exchange,其中 Load-Link 可能是也可能不是 _acquire 并且存储条件可能是也可能不是 _release。
所以我可以理解:success = _acq_rel and failure = _acquire.
我无法理解的是(除其他外):成功 = _acq_rel 和失败 = _relaxed。当然,为了实现 _acq_rel,Load-Link 必须是 _acquire 吗?如果 cmp 失败,那么降级到 _relaxed 肯定为时已晚?
success
和 failure
参数组合的预期含义是什么(当它们不相等时)?
[我总是被标准迷惑了,而且 failure
内存顺序实际上可能只是 success
内存顺序的读取一半是。]
在某些 ISA 上的 asm 中实现获取负载的一种方法是普通负载 后跟 栅栏,例如在 ARMv8 引入 ldar
/ ldaxr
之前,在像 PowerPC 或 ARM 这样的 ISA 上。 如果失败排序不包含获取,则可以跳过后面的围栏。
一个 LL/SC CAS_weak 在没有真正的 ISA 的伪 asm 中可能看起来像这样:
ll r0, mem
cmp r0, r1
jne .fail # early-out compare fail
sc r2, mem # let's pretend this sets CF condition code on SC failure
jc .fail # jump if SC failed
lwsync # LoadLoad, StoreStore, and LoadStore but not StoreLoad
... CAS success path
.fail: # we jump here without having executed any barriers
... CAS failure path
这(我认为)可能是 mem.compare_exchange_weak(r1, r2, mo_acquire, mo_relaxed);
在某些机器上的有效实现。
这只是一个获取操作,因此整个 RMW 可以使用较早的操作重新排序(LL/SC 的性质使它们在全局顺序中粘在一起)。术前无门槛,术前无门槛。
lwsync
是一个 PowerPC 屏障指令,它阻止除 StoreLoad 之外的所有重新排序(即不刷新存储缓冲区)。 https://preshing.com/20120913/acquire-and-release-semantics/ and https://preshing.com/20120930/weak-vs-strong-memory-models/
为了在大多数 ISA 上实现 CAS(..., acq_rel, relaxed)
,我们还会 运行 在 LL/SC 之前 一个屏障(将它与之前的操作,创建发布部分)。这甚至会在失败路径上执行,但 不会 创建获取语义。它不会将负载与以后的操作分开。
AFAIK 如果比较失败,您不想在 LL 和 SC 之间设置围栏以便跳过。这将延长事务并使其有更多机会因其他线程的 activity 而失败。
与往常一样,在真正的 asm 之上实现 C++ 内存模型时,您会做一些必要的事情,但不会更强,因为底层 ISA 提供的原语有限制。 大多数 ISA 不会使 CAS(acq_rel,松弛)故障路径实际上像普通松弛负载一样便宜,要么是因为这是不可能的,要么是因为它会损害正常情况下的性能。但是在某些情况下,它仍然可以 更少 比失败方需要获取语义的代价要小。
一些 ISA(如 ARM)显然只有完整的屏障(dsb ish
),所以即使 acq_rel 最终也会耗尽存储缓冲区。 (因此 ARMv8 引入获取加载和顺序释放存储 非常 很好,因为它完美匹配 C++ seq-cst 语义并且可能比屏障便宜得多。)