LLVM-原子排序无序

问题描述 投票:1回答:1

我正在一个高度依赖位操作的库中工作,其中最重要的操作是对共享内存进行操作。我还查看了LLVM的原子排序文档,并注意到unordered,它似乎比C / C ++的relaxed内存顺序还要弱。我对此有几个疑问:

  • [[无序和松弛之间有什么区别?
  • 说我有一个原子布尔,通过
  • 无序
  • 加载/存储对其进行突变是否安全?说我有一个原子位掩码,通过
  • 无序
  • 加载/存储对其进行变异是否安全?通过
  • unordered
  • fetch_and / or / xor进行变异是否安全?通过
  • 无序
  • 交换进行突变是否安全?通过
  • unordered
  • compare_and_swap进行突变是否安全吗?
llvm atomic
1个回答
0
投票
简短的答案是:无论您遇到什么问题,

无序都不大可能是解决方案!

更长的答案是...


... LLVM Language Reference Manual说:

无序

可以读取的值的集合由

happens-before部分顺序控制。除非某个操作将其写入,否则无法读取该值。这样做的目的是提供足够强的保证,以对Java的非易失性共享变量进行建模。不能为read-modify-write操作指定此顺序。它不足以使它们以任何有趣的方式成为原子。

“除非有某种操作将其写入,否则无法读取该值。”有趣 !这意味着不允许进行“投机性”写操作。假设您有if (y == 99) x = 0 ; else x = y+1 ;:优化程序可以将其转换为x = y+1 ; if (y == 99) x = 0 ;,其中x的第一个写入为“投机”。 (我并不是说这是明智或常见的优化。关键是,从单个线程的角度来看,完全可以进行的转换,对于原子来说却不可以。)C / C ++标准具有相同的限制:没有“出局”的限制。 -of-thin-air”值是允许的。

在其他地方,LLVM documentation

unordered

描述为加载/存储,这些加载/存储完成了而没有任何其他存储的中断-因此,读取值的两半(例如)的加载将not限定两半可能是两次单独写入的结果!似乎

单调

是C / C ++ memory_order_relaxed的本地名称,并描述如下:

单调

除了保证无序外,每个地址上都有一个用于通过单调操作进行修改的单一总订单。所有修改顺序必须与

happens-before顺序兼容。

unordered

不同,对于monotonic,所有线程将看到以相同顺序写入给定地址。这意味着,如果线程“ a”将“ 1”写入给定位置,然后线程“ b”写入“ 2”,则在此之后,线程“ c”和“ d”都必须读为“ 2”。 (因此,名称)。
无法保证可以将修改订单组合为整个程序的全局总订单(这通常是不可能的。

这是

放松

位。
原子

read-modify-write

操作中的读取(cmpxchgatomicrmw)在写入值之前立即按修改顺序读取该值。
类似于C / C ++:

read-modify-write不能被其他写入中断。

[如果一个原子读取发生在同一地址的另一原子读取之前,则该后来读取必须在地址的修改顺序中看到相同的值或更高的值。这不允许在同一地址上对单调(或更强)操作进行重新排序。

单调,伙计们。

[如果一个线程单调地写一个地址,而其他线程单调地重复读取该地址,则其他线程最终必须看到该写操作。

所以...在读写之间可能会有一些延迟,但是该延迟是有限的(但我认为,对于所有线程而言,可能并不相同)。 “最终”很有趣。 C11标准说:“实现应在合理的时间内使原子存储对原子负载可见。”这与之相似,但自旋更强:-)

这对应于C ++ 0x / C1x memory_order_relaxed

所以你去了。


您问:

  • 说我有一个原子布尔,通过无序加载/存储对其进行变异是否安全?
  • 说我有一个原子位掩码,通过无序加载/存储对其进行变异是否安全?
  • 它取决于您

    安全的意思:-(在任何原子负载为'x'之后,随后是原子存储为'x'的情况下,您[有多少其他存储要自加载以来一直存在'x',但是unordered

    的另一个好处是它不能保证所有线程都将以相同顺序看到对'x'的所有写入!您的其他问题无济于事,因为您不能进行unordered读-修改-写操作。

    实际上,您的x86_64保证所有写入均按相同顺序对所有线程可见-因此,所有原子操作至少都在monotonic

    / memory_order_relaxed中-没有LOCK前缀和不需要xFENCE指令,只需将MOV存入内存即可解决问题。实际上,比这更好的是,往/从内存的普通MOV得到memory_order_release / memory_order_acquire

    FWIW:您提到按位运算。我想很明显,读/写一些位会涉及读/写一些其他位的副作用。这增加了乐趣。


    我的猜测是,您至少需要进行读-修改-写操作。同样,在x86_64上,这可以归结为带有LOCK前缀的指令,这将花费10个时钟-多少个时钟取决于CPU和竞争程度。现在,mutex锁定和解锁将各自涉及LOCK ed,因此通常值得用原子级的read-modify-write(s)替换mutex_lock / ...进行一些读写... / mutex_unlock序列。 )仅当它只是一个read-modify-write。

    当然,互斥锁的缺点是,在持有互斥锁的同时,线程可以被“交换掉”:-(

    [x86_64上的自旋锁需要LOCK来获取但不释放...但是在按住自旋锁的同时被“交换”的效果更糟:-(

    © www.soinside.com 2019 - 2024. All rights reserved.