繁体   English   中英

x86 架构的内存排序限制

[英]Memory ordering restrictions on x86 architecture

Anthony Williams 在他的伟大著作《C++ 并发实践》中写道(第 309 页):

例如,在 x86 和 x86-64 架构上,原子加载操作总是相同的,无论是标记 memory_order_relaxed 还是 memory_order_seq_cst(参见第 5.3.3 节)。 这意味着使用宽松的内存排序编写的代码可能适用于具有 x86 架构的系统,而在具有更细粒度的内存排序指令集(例如 SPARC)的系统上可能会失败。

在 x86 架构上,所有原子加载操作都是memory_order_seq_cst吗? 此外,在cppreference std::memory_order站点上提到在 x86 上发布获取排序是自动的。

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

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

此外,在 x86 上“原子加载操作始终相同”并不完全准确。

在 x86 上,使用mov完成的所有加载都具有获取语义,并且使用mov完成的所有存储都具有释放语义。 所以acq_rel, acq 和relaxed load 是简单的mov s,类似acq_rel, rel 和relaxed store(acq store 和rel load 总是等于relaxed)。

然而,这不是seq_cst不一定是真的:建筑保证seq_cst语义mov 事实上,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完成, lock xadd seq_cst 存储可以简单地使用mov

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

因此,在 x86 上使用相同的指令完成 seq_cst 和获取加载通常是不正确的。 之所以如此,是因为 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 上也是如此。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM