x86-SSE指令是否具有自动释放-获取顺序?

10

根据C11-memory_order文档:http://en.cppreference.com/w/c/atomic/memory_order

C++11-std::memory_order文档也是一样的:http://en.cppreference.com/w/cpp/atomic/memory_order

在强排序系统(如x86,SPARC,IBM大型机)中,释放-获取顺序是自动的。对于这种同步模式,不会发出额外的CPU指令,只影响某些编译器优化(例如,编译器禁止将非原子存储移动到原子存储-发布之前,也禁止执行非原子加载早于原子加载-获取)。

但是对于x86-SSE指令(除了[NT] - 非临时性,我们始终必须使用L / S / MFENCE),这是真的吗?

有人在这里说“sse指令...没有反向兼容性要求,内存顺序未定义”。据信,严格的可排序性是为与旧版本的x86处理器兼容而保留的,但是新命令,即SSE(除了[NT]),自动剥夺了释放-获取顺序,是吗?


我并不是说所有的sse指令都会破坏内存排序,而是有些可能会这样做。而且gcc无法知道外部函数是否包含有问题的指令。请参见所引用文档中第8.2.5节中的建议。"Intel Core 2 Duo、Intel Atom、Intel Core Duo、Pentium 4、Intel Xeon和P6系列处理器在不使用UC内存类型时不实现强内存排序模型。" - smossen
但是,如果我们将“release-acquire ordering is automatic”称为“强内存顺序模型”,并且如果您的意思是,“release-acquire ordering is automatic”不适用于某些x86指令,并且需要MFENCE,那么对于这些某些x86指令,std::memory_order_acq_req也必须使用MOV+MFENCE,对吗? - Alex
我不确定我是否正确理解您的意思。您是否有一个示例,其中std::memory_order_acq_req与“new”指令一起使用? - smossen
@smossen 为什么你想要“new”,因为在你的例子中strcpy没有使用“new”?https://dev59.com/IWMk5IYBdhLWcg3wrwBo#19088403 但是你可以在字符串std::string* p = new std::string("Hello");的示例中看到“new” 用于Release-Acquire排序,或者如果你指的是“new SSE instructions”,那么std::string可以有它们,通过我的问题链接:http://en.cppreference.com/w/cpp/atomic/memory_order - Alex
抱歉我的表述有些混淆。我指的是一条新的x86指令,即在某些SSE扩展中引入的指令,可能会破坏强排序。 - smossen
@smossen 如果简短地说,如果顺序一致性需要 MFENCE 用于“新的SSE指令”,那么获取-释放也需要在相同的“新的SSE指令”上使用 MFENCE。如果获取-释放不需要 MFENCE,那么顺序一致性也不需要它(只需要在 STORE 后使用 SFENCE)。 - Alex
2个回答

10
以下是来自英特尔软件开发手册第三卷第8.2.2节的摘录(2014年9月版325384-052US):
  • 读操作不会与其他读操作重排序。
  • 写操作不会与旧的读操作重排序。
  • 内存写操作不会与其他内存写操作重排序,但以下例外情况除外:
    • 使用CLFLUSH指令执行的写操作;
    • 使用非临时移动指令(MOVNTI、MOVNTQ、MOVNTDQ、MOVNTPS和MOVNTPD)执行的流式存储(写操作);以及
    • 字符串操作(请参见第8.2.4.1节)。
  • 读操作可能会与旧的写操作到不同位置重排序,但不会与旧的写操作到相同位置重排序。
  • 读或写操作不能与I/O指令、锁定指令或序列化指令重排序。
  • 读操作不能超过早期的LFENCE和MFENCE指令。
  • 写操作不能超过早期的LFENCE、SFENCE和MFENCE指令。
  • LFENCE指令不能超过早期的读操作。
  • SFENCE指令不能超过早期的写操作。
  • MFENCE指令不能超过早期的读或写操作。
第一、二、三个项目描述了release-acquire ordering(发布-获取顺序),并且异常情况明确列出。您可能会发现,只有可缓存性控制指令(MOVNT*)在异常列表中,而其余的SSE / SSE2和其他向量指令遵从通用内存排序规则,并且不需要使用[LSM]FENCE

谢谢!但是MOVNTIMOVNTDQMOVNTPD是来自"emmintrin.h"的SSE2指令:https://software.intel.com/sites/products/documentation/doclib/iss/2013/compiler/cpp-lin/index.htm#GUID-7855D7EB-948F-4C4F-9442-CC821CAF83B6.htm。而`MOVNTQ`,`MOVNTPS`则是来自"xmmintrin.h"的**SSE**指令:https://software.intel.com/en-us/node/514313。或者这是否意味着只有这5个SSE(实际上还有一些 - 所有NT(非临时)SSE / AVX,其可缓存性)指令可以与其他写入重新排序,但不是所有SSE / AVX指令? - Alex
5
只有这些非时序移动 (*MOVNT*) 指令可以被重新排序,而不是 SSE 的其他指令。我将在答案中进行澄清。 - Alexey Kukanov
“read”和“write”这两个术语是否涵盖了像使用内存操作数的add指令? - Kerrek SB
@KerrekSB 是的,即使指令在ROB中被微聚合,它也会有一个负载组件分配在负载缓冲区中。 - Lewis Kelsey

2

确实,正常SSE的加载和存储指令,以及使用内存源操作数时的隐式加载,在有关排序方面的"获取"和"释放"行为上与通用寄存器的普通加载和存储具有相同的行为。

然而,它们一般无法直接用于实现大于8字节的std::atomic对象上的std::memory_order_acquire或std::memory_order_release操作,因为对于大于8字节的SSE或AVX加载和存储,没有保证原子性。缺少的保证不仅是理论上的: 有些实现(包括全新的AMD Ryzen)会将大的加载或存储分成两个较小的部分。


1 即不在已接受答案中例外清单中列出的内容: NT存储,clflush和字符串操作。


8字节SSE2的movq是原子性的。gcc -m32 -msse2使用它来实现std::atomic<int64_t>的加载/存储。请参考此Q&A中的atomic<double>相关内容:https://dev59.com/BqPia4cB1Zd3GeqPxVwO。还要注意,对L1D的原子访问并不保证缓存一致性协议会保持这些存储原子性。您链接的答案显示K10在单个套接字内具有原子16B操作,但在超传输线上在套接字之间传输行可能会在8B边界处出现tearing。 - Peter Cordes
1
@peter,你可以把上面的内容理解为只讨论大于8字节的操作。而且,我之所以引用那个答案,部分原因是因为它表明,如果制造商自己都不能保证事情,那么你甚至不应该尝试去推理硬件 - 超传输孔就是一个很好的例子(这扰乱了所有基于总线宽度和缓存行为的推理)。 - BeeOnRope
顺便说一句,我认为对齐的向量加载/存储不会在单个4B / 8B元素内引起撕裂。 因此,您可以在atomic <int> shared_array []上使用SIMD进行mo_relaxed操作。 英特尔手册绝对不能保证这一点,只是说更宽的向量可能需要多次访问来处理。 - Peter Cordes
我们知道(至少对于没有掩码页面错误的情况),vmaskmovps 在 Haswell 上是 1 个加载端口 uop 和 2 个 port5 uops。这基本上告诉我们什么都没有,因为我们已经知道 Haswell 可以原子地从 L1D 缓存行中读取 32B,这比为每个元素分别进行 4B 加载具有更高的原子性。它并没有告诉我们 CPU 是否需要在每个元素内部是原子的,尽管我认为这是暗示的。存储情况是单个 store-addr/store-data uop 对加上两个 ALU uops。 - Peter Cordes
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接