向量加载/存储和收集/分散的每元素原子性?

时间:2017-09-02 09:56:12

标签: x86 atomic sse avx avx512

考虑像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加载的元素原子,那么这也是一个有用的答案。)

  1. 脚注:x86原子性基础:
  2. 根据英特尔和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原子载荷或商店,你可以使用vpmaskmovmovdqa这样的gcc那样攻击一个,但那个可怕的性能。另见Atomic double floating point or SSE/AVX vector load/store on x86_64。)

0 个答案:

没有答案