考虑以下代码:
std::atomic<int> counter;
/* otherStuff 1 */
counter.fetch_add(1, std::memory_order_relaxed);
/* otherStuff 2 */
x86-64中是否存在一条指令(例如,使用不到5年的体系结构),该指令将允许其他东西1和2在fetch_add
上重新排序,还是总是要序列化? >
编辑:
“ lock add
是x86上的内存壁垒吗?”来概括一下。似乎不是,尽管我不确定在哪里可以找到它的参考。
答案 0 :(得分:4)
首先让我们看一下使用std::memory_order_relaxed
时允许编译器执行的操作。
如果otherStuff 1/2
与原子操作之间没有依赖关系,则可以肯定地对语句重新排序。例如:
g = 3;
a.fetch_add(1, memory_order_relaxed);
g += 12;
clang ++生成以下程序集:
lock addl $0x1,0x2009f5(%rip) # 0x601040 <a>
movl $0xf,0x2009e7(%rip) # 0x60103c <g>
这里clang随意使用原子g = 3
操作对fetch_add
重新排序,这是合法的转换。
使用std::memory_order_seq_cst
时,编译器输出变为:
movl $0x3,0x2009f2(%rip) # 0x60103c <g>
lock addl $0x1,0x2009eb(%rip) # 0x601040 <a>
addl $0xc,0x2009e0(%rip) # 0x60103c <g>
不会对语句进行重新排序,因为不允许编译器这样做。 读-修改-写(RMW)操作上的顺序一致顺序既是释放操作又是获取操作,因此,不允许在编译器和CPU级别上对语句进行(可见的)重新排序。
您的问题是,在X86-64
上,std::atomic::fetch_add
使用宽松的排序是否是序列化操作。.
答案是:是的,如果您不考虑编译器的重新排序。
在X86
架构上,RMW操作始终刷新存储缓冲区,因此实际上是序列化和顺序一致的操作。
您可以说,在X86
CPU上,每个RMW操作:
答案 1 :(得分:0)
在X86架构上,RMW操作始终会刷新商店 缓冲区,因此实际上是序列化和顺序 一致的操作。
我希望人们不再这么说。
该语句甚至没有任何意义,因为不存在“顺序一致操作”之类的东西,因为“顺序一致”不是任何操作的属性。顺序一致的执行是最终结果是执行结果的操作交错的执行。
这些RMW操作可以怎么说:
这是RMW之前的部分,RMW和之后的部分按顺序运行。换句话说,RMW前后都有完整的栅栏。
是否导致顺序执行整个程序取决于程序的所有全局可见操作的性质。
这是可见性 。我不知道这些处理器是否尝试在RMW之后尝试推测性地执行代码,但要满足正确性要求,即如果在并行执行方面存在副作用,则回滚操作(这些细节对于不同的供应商和除非明确说明,否则给定家庭中的几代人。
您的问题的答案是否会有所不同
问题标题是“ std::atomic::fetch_add
是否在x86-64上进行序列化操作?”一般形式:
“ OP是否在ARCH上提供保证P”
其中
通常,规范的答案是:这个问题没有道理,因为OP是高级的且独立于目标。 此处存在低/高不匹配。
编译器受语言标准(或更确切地说是其最合理的解释),文档扩展,历史...而不是目标体系结构标准的约束,除非该功能是低级,透明的高级语言的功能。
在C / C ++中获取底层语义的规范方法是使用易失性对象和易失性操作。
在这里,您甚至必须使用volatile std::atomic<int>
才能提出有关体系结构保证的有意义的问题。
您的问题的有意义的变体将使用以下代码:
volatile std::atomic<int> counter;
/* otherStuff 1 */
counter.fetch_add(1, std::memory_order_relaxed);
该语句将生成一个原子RMW操作,在这种情况下,该操作在CPU上是“序列化操作”:在汇编代码中,在RMW启动之前完成的所有操作; RMW之后的所有操作都等到RMW完成启动(就可见性而言)。
然后,您需要了解volatile语义的不愉快之处:volatile仅适用于这些volatile操作,因此您仍将无法获得有关其他操作的一般保证。
不能保证在汇编代码中先对易失RMW操作进行排序之前的高级C ++操作。为此,您将需要一个“编译器障碍”。这些障碍不是可移植的。 (并且这里不需要,因为无论如何这都是一种愚蠢的方法。)
但是,如果您想要该保证,则可以使用:
所以为什么不让您的RMW操作ack_rel?那么它甚至不需要变得不稳定。
x86-64中是否有说明(例如,小于5岁 架构)
指令集的潜在变体是另一个子问题。供应商可以引入新的说明以及在运行时测试其可用性的方法;而且编译器甚至可以生成代码来检测其可用性。
任何会遵循现有传统(1)对该家族中通常记忆操作进行有序排序的RMW功能都必须遵守这些传统:
那么任何新的(但传统的)RMW操作都必须既是获取操作又是释放操作。
(将来可能添加的潜在假想RMW操作示例为xmult
和xdiv
。)
但是,这是未来主义,将来添加较少顺序的指令不会违反任何安全性不变,除非可能针对基于定时的侧通道类似Spectre的攻击,否则我们通常都不知道如何建模和推理
这些问题(甚至是关于当前问题)的问题是,需要提供缺席证明,为此,我们需要了解CPU系列的每个变体。这并不总是可行的,如果在高级代码中使用正确的顺序,则不必要,如果不使用,则没用。
(1)保证内存操作的传统是CPU设计中的准则,而不是有关任何功能操作的保证:根据定义,尚不存在的操作除了保证内存完整性外,还不能保证其语义。是,保证将来没有操作会破坏先前建立的特权和安全保证(将来创建的任何非特权指令都不能访问未映射的内存地址...。
答案 2 :(得分:-1)
使用std::memory_order_relaxed
时,唯一的保证就是操作是原子的。编译器或CPU均可随意对操作进行任何排序。
来自https://en.cppreference.com/w/cpp/atomic/memory_order:
轻松的操作:没有同步或排序 施加于其他读取或写入的约束,仅此操作的 保证原子性(请参见下面的放宽顺序)