考虑像atomic<int32_t> shared_array[]
这样的数组。如果你想SIMD矢量化for(...) sum += shared_array[i].load(memory_order_relaxed)
怎么办?或者在数组中搜索第一个非零元素,或者将其范围归零?它可能很少见,但考虑不允许在元素内部撕裂的任何用例,但在元素之间重新排序是正常的。(也许是搜索找到CAS的候选者)。
我认为 x86对齐的向量加载/存储在实践中可以安全地用于具有mo_relaxed
操作的SIMD,因为任何撕裂只会发生在当前硬件上最坏的8B边界(因为那是自然对齐的8B访问原子 1 的原因)。不幸的是,英特尔的手册只说:
&#34; x87指令或访问大于四字的数据的SSE指令可以使用多个存储器访问来实现。&#34;
不保证这些组件访问是自然对齐,不重叠或其他任何内容。 (有趣的事实:x87 10字节fld m80
加载完成了2个加载uops和2个ALU uops on Haswell,according to Agner Fog,大概是qword + word。)
如果您希望以面向未来的方式进行矢量化,而当前的x86手册将说明将适用于所有未来的x86 CPU,您可以使用movq
/ movhps
加载/存储在8B块中。
或者你可以使用带有全真掩码的256b vpmaskmovd
,因为本手册的“操作”部分根据多个单独的32位加载来定义它,例如{{ 1}}。这是否意味着每个元素都作为一个单独的32位访问,保证该元素内的原子性?
(在真实的硬件上,Haswell上的1次加载和2次5次uop,或Ryzen上只有1或2次加载+ ALU uops(128/256)。我认为这是针对的情况不需要从进入未映射页面的元素中抑制异常,因为它可能更慢(但如果它需要微代码辅助,则为IDK)。无论如何,这告诉我们它至少与正常情况一样原子{ {1}}加载Haswell,但这并没有告诉我们x86 Deathstation 9000,其中16B / 32B向量访问被分解为单字节访问,因此每个元素内都可能会撕裂。
我认为实际上可以安全地假设你不会在16,32或64位元素中撕裂任何真实的对齐向量加载/存储x86 CPU,因为对于一个已经必须保持自然对齐的64位标量存储原子的有效实现是没有意义的,但知道手册中的保证有多远是有趣的实际上去。)
收集(AVX2,AVX512)/分散(AVX512)
Load_32(mem + 4)
之类的指令显然由多个独立的32b或64b访问组成。 AVX2形成is documented做多个vmovdqa
所以大概通过原子性保证来覆盖它,如果它不跨越边界,每个元素将以原子方式收集。
AVX512收集are documented in Intel's PDF insn ref manual作为
每个元素分别vpgatherdd
。 (订购:元素可以按任何顺序收集,但是故障必须按从右到左的顺序进行。内存订购和其他说明遵循英特尔 -
64内存订购模型。)
AVX512 散布以相同的方式记录(prev链接的第1802页)。没有提到原子性,但它们确实涵盖了一些有趣的极端情况:
如果两个或多个目标索引完全重叠,则可以跳过“较早”的写入。
元素可能以任何顺序分散,但故障必须按从右到左的顺序传递
如果此指令覆盖自身然后出错,则之前只能完成一部分元素 故障传递(如上所述)。如果错误处理程序完成并尝试重新执行此操作 指令,新指令将被执行,分散将无法完成。
只保证对重叠向量索引的写入相互排序(从LSB到 MSB的源寄存器)。请注意,这还包括部分重叠的矢量索引。写的不是 重叠可能以任何顺序发生。使用其他指令进行内存排序遵循Intel-64内存 订购模式。请注意,这不会考虑映射到同一物理的非重叠索引 地址位置。
(即因为相同的物理页面被映射到两个不同虚拟地址的虚拟内存中。因此,重叠检测允许在地址转换之前(或与之并行)发生,而不需要在之后重新检查。)
我包括了最后两个因为他们有趣的角落案例,我甚至都没想过要这么做。自修改案例很有趣,但我认为FETCH_32BITS(DATA_ADDR);
会有同样的问题(它也可以中断,使用DEST[i+31:i] <- MEM[BASE_ADDR + SignExtend(VINDEX[i+31:i]) * SCALE + DISP]), 1)
跟踪进度)。
我认为原子性是Intel-64内存排序模型的一部分,所以他们提到它而不说别的事实似乎暗示每元素访问是原子的。 (几乎可以肯定地收集两个相邻的4B元素并不算作单个8B访问。)
x86手册保证哪些向量加载/存储指令在每个元素的基础上是原子的?
真实硬件上的实验测试几乎肯定会告诉我,我的Skylake CPU上的一切都是原子的,而这不是这个问题的关键所在。 我询问我对手册的解释是否适用于rep stosd
/ rcx
加载,以及收集/分散。
(如果有任何理由怀疑真正的硬件将继续成为简单vmaskmov
加载的元素原子,那么这也是一个有用的答案。)
根据英特尔和AMD手册,在x86中,自然对齐的加载和存储为8B或更窄are guaranteed to be atomic。事实上,对于缓存访问,任何不跨越8B边界的访问也是原子的。 (在英特尔P6及更高版本上提供比AMD更强的保证:在高速缓存行(例如64B)内未对齐是缓存访问的原子)。
16B或更宽的矢量加载/存储不保证是原子的。它们位于某些CPU上(至少对于观察者是其他CPU时的缓存访问),但即使对L1D缓存进行16B范围的原子访问也不会使其成为原子。例如,AMD K10 Opterons introduces tearing between halves of an aligned 16B vector的套接字之间的HyperTransport一致性协议,即使在相同套接字(物理CPU)中的线程上进行测试也没有显示撕裂。
(如果你需要一个完整的16B原子载荷或商店,你可以使用vpmaskmov
像movdqa
这样的gcc那样攻击一个,但那个可怕的性能。另见Atomic double floating point or SSE/AVX vector load/store on x86_64。)