x86 体系结构上的内存排序限制

Memory ordering restrictions on x86 architecture

本文关键字:排序 内存 结构上 x86      更新时间:2023-10-16
安东尼

·威廉姆斯(Anthony Williams(在他的伟大著作《行动中的C++并发》( Concurrency in Action(中写道(第309页(:

例如,在 x86

和 x86-64 体系结构上,原子加载操作是 始终相同,无论是标记为memory_order_relaxed还是memory_order_seq_cst (请参阅第 5.3.3 节(。这意味着使用宽松内存排序编写的代码可能会 在具有 x86 架构的系统上运行,在具有更精细的系统上会失败 一组粒度的内存排序指令,如 SPARC。

我是否正确理解在 x86 架构上所有原子加载操作都是memory_order_seq_cst?此外,在 cpp首选项std::memory_order站点上提到,在 x86 上,发布获取排序是自动的。

如果此限制有效,排序是否仍适用于编译器优化?

是的,排序仍然适用于编译器优化。

此外,在x86上"原子加载操作总是相同的"并不完全准确。

在 x86 上,使用 mov 完成的所有加载都具有获取语义,使用 mov 完成的所有存储都具有发布语义。 因此,acq_rel,acq 和松弛负载是简单的mov,类似地acq_rel、rel 和放松存储(acq 存储和 rel 负载总是等于松弛(。

然而,对于seq_cst来说,这不一定是正确的:架构不能保证mov seq_cst语义。 实际上,x86 指令集没有任何用于顺序一致的加载和存储的特定指令。 只有 x86 上的原子读取-修改-写入操作才具有seq_cst语义。 因此,您可以通过执行参数为 0 的fetch_and_add操作(lock xadd指令(来获取加载seq_cst语义,并通过执行seq_cst交换操作(xchg指令(并丢弃以前的值来seq_cst存储语义。

但你不需要两者兼而有之! 只要所有seq_cst存储都用xchg完成,seq_cst加载就可以简单地用mov来实现。 对偶,如果所有负载都用lock xadd完成,seq_cst存储可以简单地用mov来实现。

xchglock xaddmov慢得多. 因为程序的负载(通常(比存储多,所以使用xchg来seq_cst存储很方便,这样(更频繁的(seq_cst加载可以简单地使用mov。 此实现详细信息在 x86 应用程序二进制接口 (ABI( 中编码。 在 x86 上,兼容的编译器必须将seq_cst存储编译为xchg,以便可以使用更快的mov指令完成seq_cst加载(可能出现在另一个翻译单元中,使用不同的编译器编译(。

因此,一般来说

,seq_cst和采集负载是用 x86 上的相同指令完成的。 这才是正确的,因为 ABI 指定将seq_cst存储编译为xchg

编译器当然必须遵循语言的规则,无论它运行在什么硬件上。

他说的是,在x86上,你没有宽松的排序,所以即使你不要求,你也会得到更严格的排序。这也意味着在 x86 上测试的此类代码可能无法在具有宽松排序的系统上正常工作。

值得记住的是,尽管加载放松和seq_cst加载可能会映射到 x86 上的相同指令,但它们并不相同。 编译器可以跨内存操作将放松的负载自由地重新排序到不同的内存位置,而seq_cst加载不能跨其他内存操作重新排序。

书中的句子写得有些误导。在体系结构上获得的排序不仅取决于如何转换原子负载,还取决于如何转换原子存储。

在 x86 上实现seq_cst的常用方法是在任何seq_cst存储和来自同一线程的后续seq_cst加载之间的某个点刷新存储缓冲区。编译器保证这一点的常用方法是在存储后刷新,因为存储比加载少。在此转换中,seq_cst加载不需要刷新。

如果仅使用普通加载和存储对 x86 进行编程,则保证加载提供acquire语义,而不是seq_cst

至于编译器优化,在 C11/C++11 中,编译器在考虑底层硬件之前,会根据基于特定原子组学语义的代码移动进行优化。(硬件可能会提供更强的排序,但编译器没有理由因此限制其优化。

我是否正确,在 x86 架构上所有原子负载 操作是否memory_order_seq_cst

只有执行(程序、程序中某些线程间可见操作的(可以是顺序的。单个操作本身不是顺序的。

询问单个隔离操作的实现是否是顺序的是一个毫无意义的问题。

所有需要某种保证的内存操作的转换必须按照启用该保证的策略来完成。可能有不同的策略,这些策略具有不同的编译器复杂性成本和运行时成本。

[只是实现虚拟函数有不同的策略:唯一可以的(符合我们对速度、可预测性和简单性的所有期望(是使用 vtables,所以所有编译器都使用 vtable,但虚拟函数没有定义为通过 vtable。

在实践中,没有用于在给定 CPU 上实现memory_order_seq_cst操作的有很大不同策略(据我所知(。编译器之间的差异很小,不会妨碍二进制兼容性。但是存在潜在的差异,多线程程序的高级全局优化可能会为原子操作的更高效代码生成提供新的机会。

根据您的编译器,仅包含松弛加载和std::atomic<>对象memory_order_seq_cst修改的程序可能仅显示顺序行为,也可能不具有顺序行为,即使在强顺序 CPU 上也是如此。