LLVM - 原子排序无序
LLVM - Atomic Ordering Unordered
我正在开发一个严重依赖按位运算的库,其中最重要的运算在共享内存上进行。我还查看了 LLVM 的原子排序文档并注意到 unordered,它似乎比 C/C++ 的 relaxed[=33] 更弱=] 记忆顺序。我有几个问题:
- 无序和宽松有什么区别?
- 假设我有一个原子布尔值,通过 unordered load/store 来改变它安全吗?
- 假设我有一个原子位掩码,通过 unordered load/store 对其进行变异安全吗?
- 通过 unordered 对其进行变异是否安全 fetch_and/or/xor?
- 通过 unordered swap 对其进行变异是否安全?
- 通过 unordered compare_and_swap 对其进行变异是否安全?
简短的回答是:无论你的问题是什么,unordered 都不太可能成为解决方案!
较长的答案是...
...LLVM Language Reference Manual 说:
unordered
The set of values that can be read is governed by the happens-before partial order. A value cannot be read unless some operation wrote it. This is intended to provide a guarantee strong enough to model Java’s non-volatile shared variables. This ordering cannot be specified for read-modify-write operations; it is not strong enough to make them atomic in any interesting way.
“除非某些操作写入值,否则无法读取该值。”有趣 !这意味着不允许“推测性”写入。假设您有 if (y == 99) x = 0 ; else x = y+1 ;
:优化器可以将其转换为 x = y+1 ; if (y == 99) x = 0 ;
,其中 x
的第一次写入是“推测”的。 (我并不是说这是一个明智的或常见的优化。关键是从单线程的角度来看完全可以的转换对于原子来说是不行的。)C/C++ 标准有相同的限制: 不允许“凭空”值。
在别处,LLVM documentation 将 unordered 描述为 loads/stores,它在没有任何其他存储中断的情况下完成——所以读取两半的负载(比如) 的值 not 如果两半可能是两次单独写入的结果 !
似乎 monotonic 是 C/C++ memory_order_relaxed
的本地名称,描述为:
monotonic
In addition to the guarantees of unordered, there is a single total order for modifications by monotonic operations on each address. All modification orders must be compatible with the happens-before order.
与无序不同,单调所有线程都将以相同的顺序看到对给定地址的写入。这意味着如果线程 'a' 将“1”写入给定位置,然后线程 'b' 写入“2”,那么之后线程 'c' 和 'd' 必须都读取'2'。 (因此得名。)
There is no guarantee that the modification orders can be combined to a global total order for the whole program (and this often will not be possible).
这是轻松位。
The read in an atomic read-modify-write operation (cmpxchg
and atomicrmw
) reads the value in the modification order immediately before the value it writes.
与C/C++相同:读取-修改-写入不能被另一个写入中断。
If one atomic read happens before another atomic read of the same address, the later read must see the same value or a later value in the address’s modification order. This disallows reordering of monotonic (or stronger) operations on the same address.
这很单调,伙计们。
If an address is written monotonic-ally by one thread, and other threads monotonic-ally read that address repeatedly, the other threads must eventually see the write.
所以...在写入和读取之间可能存在一些延迟,但该延迟是有限的(但我认为可能并非所有线程都相同)。 “最终”很有趣。 C11 标准说“实现应该使原子存储在合理的时间内对原子负载可见。”,这很相似,但有一个更积极的旋转:-)
This corresponds to the C++0x/C1x memory_order_relaxed
.
好了。
您问的是:
- 假设我有一个原子布尔值,通过无序 load/store 对其进行变异安全吗?
- 假设我有一个原子位掩码,通过无序 load/store 对其进行变异安全吗?
这取决于您所说的 safe 的意思:-( 任何 'x' 的原子加载随后是 'x' 的原子存储,您 不知道 自加载以来 'x' 还有多少其他商店。但是, unordered 的额外乐趣在于它不保证所有线程都会以相同的顺序看到对 'x' 的所有写入!
你的其他问题没有实际意义,因为你不能无序读-修改-写操作。
作为一个实际问题,您的 x86_64 保证所有写入都以相同的顺序对所有线程可见——因此所有原子操作至少是 monotonic/memory_order_relaxed
-- 不需要 LOCK
前缀和 xFENCE
指令,简单的 MOV
到内存就可以了。事实上,比这更好的是,普通 MOV
to/from 内存提供 memory_order_release
/memory_order_acquire
.
FWIW:您提到了按位运算。我想很明显 reading/writing 一些位将涉及 reading/writing 一些其他位作为副作用。这增加了乐趣。
我的猜测是,您至少需要执行读-修改-写操作。同样,在 x86_64 上,这归结为带有 LOCK
前缀的指令,这将花费 10 个时钟——多少个 10 取决于 CPU 和争用程度。现在,一个 mutex
锁定和解锁将分别涉及一个 LOCK
ed,所以通常值得更换一个 mutex_lock/...做一些阅读和写作.../mutex_unlock 仅当它只是一个读-修改-写时才按原子读-修改-写顺序排列。
互斥锁的缺点当然是,线程在持有互斥锁时可以被“换出”:-(
自旋锁(在 x86_64 上)需要 LOCK
才能获取但不能释放...但是持有自旋锁时被“换出”的效果是均匀的更糟:-(
我正在开发一个严重依赖按位运算的库,其中最重要的运算在共享内存上进行。我还查看了 LLVM 的原子排序文档并注意到 unordered,它似乎比 C/C++ 的 relaxed[=33] 更弱=] 记忆顺序。我有几个问题:
- 无序和宽松有什么区别?
- 假设我有一个原子布尔值,通过 unordered load/store 来改变它安全吗?
- 假设我有一个原子位掩码,通过 unordered load/store 对其进行变异安全吗?
- 通过 unordered 对其进行变异是否安全 fetch_and/or/xor?
- 通过 unordered swap 对其进行变异是否安全?
- 通过 unordered compare_and_swap 对其进行变异是否安全?
简短的回答是:无论你的问题是什么,unordered 都不太可能成为解决方案!
较长的答案是...
...LLVM Language Reference Manual 说:
unordered
The set of values that can be read is governed by the happens-before partial order. A value cannot be read unless some operation wrote it. This is intended to provide a guarantee strong enough to model Java’s non-volatile shared variables. This ordering cannot be specified for read-modify-write operations; it is not strong enough to make them atomic in any interesting way.
“除非某些操作写入值,否则无法读取该值。”有趣 !这意味着不允许“推测性”写入。假设您有 if (y == 99) x = 0 ; else x = y+1 ;
:优化器可以将其转换为 x = y+1 ; if (y == 99) x = 0 ;
,其中 x
的第一次写入是“推测”的。 (我并不是说这是一个明智的或常见的优化。关键是从单线程的角度来看完全可以的转换对于原子来说是不行的。)C/C++ 标准有相同的限制: 不允许“凭空”值。
在别处,LLVM documentation 将 unordered 描述为 loads/stores,它在没有任何其他存储中断的情况下完成——所以读取两半的负载(比如) 的值 not 如果两半可能是两次单独写入的结果 !
似乎 monotonic 是 C/C++ memory_order_relaxed
的本地名称,描述为:
monotonic
In addition to the guarantees of unordered, there is a single total order for modifications by monotonic operations on each address. All modification orders must be compatible with the happens-before order.
与无序不同,单调所有线程都将以相同的顺序看到对给定地址的写入。这意味着如果线程 'a' 将“1”写入给定位置,然后线程 'b' 写入“2”,那么之后线程 'c' 和 'd' 必须都读取'2'。 (因此得名。)
There is no guarantee that the modification orders can be combined to a global total order for the whole program (and this often will not be possible).
这是轻松位。
The read in an atomic read-modify-write operation (
cmpxchg
andatomicrmw
) reads the value in the modification order immediately before the value it writes.
与C/C++相同:读取-修改-写入不能被另一个写入中断。
If one atomic read happens before another atomic read of the same address, the later read must see the same value or a later value in the address’s modification order. This disallows reordering of monotonic (or stronger) operations on the same address.
这很单调,伙计们。
If an address is written monotonic-ally by one thread, and other threads monotonic-ally read that address repeatedly, the other threads must eventually see the write.
所以...在写入和读取之间可能存在一些延迟,但该延迟是有限的(但我认为可能并非所有线程都相同)。 “最终”很有趣。 C11 标准说“实现应该使原子存储在合理的时间内对原子负载可见。”,这很相似,但有一个更积极的旋转:-)
This corresponds to the C++0x/C1x
memory_order_relaxed
.
好了。
您问的是:
- 假设我有一个原子布尔值,通过无序 load/store 对其进行变异安全吗?
- 假设我有一个原子位掩码,通过无序 load/store 对其进行变异安全吗?
这取决于您所说的 safe 的意思:-( 任何 'x' 的原子加载随后是 'x' 的原子存储,您 不知道 自加载以来 'x' 还有多少其他商店。但是, unordered 的额外乐趣在于它不保证所有线程都会以相同的顺序看到对 'x' 的所有写入!
你的其他问题没有实际意义,因为你不能无序读-修改-写操作。
作为一个实际问题,您的 x86_64 保证所有写入都以相同的顺序对所有线程可见——因此所有原子操作至少是 monotonic/memory_order_relaxed
-- 不需要 LOCK
前缀和 xFENCE
指令,简单的 MOV
到内存就可以了。事实上,比这更好的是,普通 MOV
to/from 内存提供 memory_order_release
/memory_order_acquire
.
FWIW:您提到了按位运算。我想很明显 reading/writing 一些位将涉及 reading/writing 一些其他位作为副作用。这增加了乐趣。
我的猜测是,您至少需要执行读-修改-写操作。同样,在 x86_64 上,这归结为带有 LOCK
前缀的指令,这将花费 10 个时钟——多少个 10 取决于 CPU 和争用程度。现在,一个 mutex
锁定和解锁将分别涉及一个 LOCK
ed,所以通常值得更换一个 mutex_lock/...做一些阅读和写作.../mutex_unlock 仅当它只是一个读-修改-写时才按原子读-修改-写顺序排列。
互斥锁的缺点当然是,线程在持有互斥锁时可以被“换出”:-(
自旋锁(在 x86_64 上)需要 LOCK
才能获取但不能释放...但是持有自旋锁时被“换出”的效果是均匀的更糟:-(