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