Intel在明显重叠的内存区域上存储指令

问题描述 投票:1回答:1

我必须将YMM寄存器中的低3个double存储到大小为3的未对齐double数组中(也就是说,无法写入第4个元素)。但是有点顽皮,我想知道AVX内部_mm256_storeu2_m128d是否可以解决问题。我有

reg = _mm256_permute4x64_pd(reg, 0b10010100); // [0 1 1 2]
_mm256_storeu2_m128d(vec, vec + 1, reg);

并通过clang编译给出

vmovupd xmmword ptr [rsi + 8], xmm1 # reg in ymm1 after perm
vextractf128    xmmword ptr [rsi], ymm0, 1

如果storeu2具有类似于memcpy的语义,那么它无疑会触发未定义的行为。但是使用生成的指令,这将不会出现比赛条件(或其他潜在问题)吗?

也欢迎将YMM存储到3号数组中的其他方法。

c++ intrinsics avx
1个回答
2
投票

除了英特尔作为文档发布的内容外,实际上没有针对英特尔内部函数AFAIK的正式规范。例如他们的内在指导。还有白皮书中的示例等;例如需要工作的示例是GCC / c知道必须使用__m128定义__attribute__((may_alias))的一种方式。

全部都在一个线程中,完全同步,因此绝对没有“竞争条件”。在您的情况下,存储的顺序无关紧要(假设它们不与__m256d reg对象本身重叠!这等效于重叠的memcpy问题。)您正在做的事情可能像两个indeterminately sequenced内存到重叠的目的地:它们肯定以一种顺序或另一种顺序发生,并且编译器可以选择两者之一。

存储顺序的可观察到的区别是性能:如果您想在之后不久进行SIMD重装,则如果16字节重装从一个16字节存储中获取其数据,而不是与之重叠,则存储转发将更好地工作。两家商店。

但是,一般来说,重叠的商店可以提高性能;存储缓冲区将吸收它们。但是,这意味着其中之一是未对齐的,并且越过缓存行边界会更加昂贵。


但是,仅此而已:Intel's intrinsics guide does list an "operation" section for that compound intrinsic

操作

MEM[loaddr+127:loaddr] := a[127:0]
MEM[hiaddr+127:hiaddr] := a[255:128]

因此,它严格地被定义为首先是低地址存储(第二个arg;我认为您倒过来了)。


而且所有这些都没有意义,因为有一种更有效的方法

您的路线要花费1个过路点洗牌+ vmovups + vextractf128 [mem], ymm, 1。根据其编译方式,这两个存储区都必须在洗牌之后才能开始。 (尽管看起来像是clang可能避免了该问题)。

在Intel CPU上,vextractf128 [mem], ymm, imm的前端成本为2 uops,not

微融合为一个。 (由于某种原因,Zen也有2 uops。)

在Zen 2之前的AMD CPU上,交叉道洗牌超过1 uop,因此_mm256_permute4x64_pd的价格比必要的昂贵。

您只想存储输入向量的低通道和高通道的低元素

。最便宜的随机播放是Zen上的vextractf128 xmm, ymm, 1-1 uop / 1c延迟(无论如何,它将YMM向量分成两个128位的一半)。它与Intel上的任何其他跨界洗牌一样便宜。

您希望编译器生成的asm可能就是这个,它仅需要AVX1。 AVX2对此没有任何有用的说明。

    vextractf128  xmm1, ymm0, 1            ; single uop everywhere
    vmovupd       [rdi], xmm0              ; single uop everywhere
    vmovsd        [rdi+2*8], xmm1          ; single uop everywhere

所以您需要这样的东西,应该可以有效地编译。

    _mm_store_pd(vec, _mm256_castpd256_pd128(reg));  // low half
    __m128d hi = _mm256_extractf128_pd(reg, 1);
    _mm_store_sd(vec+2, hi);
    // or    vec[2] = _mm_cvtsd_f64(hi);

[vmovlps_mm_storel_pi)也可以,但是使用AVX VEX编码不会节省任何代码大小,并且需要更多的转换才能使编译器满意。

不幸的是,没有vpextractq [mem], ymm,只有XMM源,因此无济于事。


伪装店:

如评论中所讨论,是的,您可以执行vmaskmovps,但是不幸的是,它效率不如我们在所有CPU上所希望的那样。在AVX512使掩盖的负载/存储成为一等公民之前,最好洗牌并存储2个存储。或者填充您的数组/结构,以便您至少可以暂时踩到以后的内容。

Zen具有2 uop vmaskmovpd ymm负载,但非常

昂贵的vmaskmovpd存储(42 uop,YMM每11个周期1个)。或Zen +和Zen2为18或19 uops,6个周期的吞吐量。 如果您完全关心Zen,请避免使用vmaskmov

[在Intel Broadwell和更早的版本上,根据vmaskmov测试,Agner's Fog's存储为4 oups,因此融合域uop比我们从shuffle + movups + movsd得到的多1。但是,尽管如此,Haswell和更高版本仍然管理1时钟吞吐量,因此,如果这是一个瓶颈,那么它将超过2家商店的2周期吞吐量。对于256位存储,即使没有屏蔽,SnB / IvB当然也需要2个周期。

[在Skylake上,vmaskmov mem, ymm, ymm is only 3 uops(Agner Fog列出了4,但是他的电子表格是手动编辑的,以前是错误的。我认为可以假定uops.info的自动测试是正确的。这很有意义;这很有意义; Skylake-client是基本与Skylake-AVX512相同的内核,只是没有实际启用AVX512。因此,他们可以通过将vmaskmov mem, ymm, ymm解码为测试到掩码寄存器(1 uop)+掩码存储(另外2 oups,无需微融合)来实现。

因此,如果您只关心Skylake及更高版本,并且可以分摊将掩码加载到矢量寄存器中的成本(可重用于加载和存储,则vmaskmovpd实际上是相当不错的。]

相同的前端成本但在后端便宜:每个商店地址和商店数据仅1个,而不是2个单独的商店。 注意Haswell及更高版本的1 /时钟吞吐量与进行2个单独存储的2周期吞吐量。

vmaskmovpd甚至可以有效地存储到屏蔽的重新加载;我认为英特尔在其优化手册中提到了一些相关内容。

© www.soinside.com 2019 - 2024. All rights reserved.