两个256位整数的按位xor

时间:2015-12-17 09:48:50

标签: sse simd avx

我有一个AVX cpu(不支持AVX2),我想计算两个256位整数的按位xor。

由于_mm256_xor_si256仅在AVX2上可用,我可以使用__m256将这256位加载为_mm256_load_ps,然后执行_mm256_xor_ps。这会产生预期的结果吗?

我主要担心的是,如果内存内容不是有效的浮点数,_mm256_load_ps不会将寄存器中的位加载到与内存中完全相同的位置吗?

感谢。

3 个答案:

答案 0 :(得分:12)

首先,如果您使用256b整数进行其他操作(例如加/减/乘),将它们放入向量寄存器仅用于偶尔的XOR可能不值得转移它们的开销。如果寄存器中已有两个数字(使用最多8个寄存器),则只需要四条xor指令来获取结果(如果需要避免覆盖目标,则需要4条mov指令)。破坏性版本可以在SnB上每1.33个时钟周期运行一次,或者在Haswell及更高版本上每个时钟运行一个。 (xor可以在4个ALU端口中的任何一个上运行)。因此,如果您只是在某些xor或其他任何内容之间执行单个add/adc,请坚持使用整数。

以64b块存储到内存然后执行128b或256b加载cause a store-forwarding failure,再添加几个延迟周期。使用movq / pinsrq会比xor花费更多的执行资源。走另一条路并不是坏事:256b商店 - > 64b负载适用于商店转发。 movq / pextrq仍然很糟糕,但会有更低的延迟(以更多uops为代价)。

FP加载/存储/按位操作在架构上保证不会生成FP异常,即使在表示信令NaN的位模式上使用时也是如此。只有实际的FP数学指令才会列出数学异常:

  

VADDPS

     

SIMD浮点例外
   溢出,下溢,无效,   精确,非常规。

  

VMOVAPS

     

SIMD浮点例外
  无。

(来自英特尔的insn ref手册。请参阅 wiki以获取该内容和其他内容的链接。)

在英特尔硬件上,加载/存储的风格可以在没有额外延迟的情况下进入FP或整数域。无论数据来自何处,AMD都会使用相同的加载/存储风格。

不同风格的向量移动指令actually matter for register<-register moves。在Intel Nehalem上,使用错误的mov指令可能会导致旁路延迟。在AMD Bulldozer系列中,移动是通过寄存器重命名处理而不是实际复制数据(如Intel IvB及更高版本),dest寄存器继承了src寄存器写入的域。

我所读过的现有设计与movapd的处理movaps完全不同。据推测,英特尔为解码简单性创建movapd与未来规划一样多(例如,允许设计的可能性,其中有双域和单域,具有不同的转发网络)。 (movapdmovaps,前缀为66h,就像每个其他SSE指令的双版本都添加66h前缀字节一样。或F2而不是F3标量指令。)

显然AMD设计了带有辅助信息的FP矢量标签,因为Agner Fog found使用addps的输出作为addpd的输入时会有很大的延迟。我不认为movaps两个addpd指令之间的xorps,甚至xorps都会导致这个问题:只有实际的FP数学。 (FP bitwise boolean ops是Bulldozer-family上的整数域。)

Intel SnB / IvB(唯一具有AVX而非AVX2的Intel CPU)的理论吞吐量:

使用AVX VMOVDQU ymm0, [A] VXORPS ymm0, ymm0, [B] VMOVDQU [result], ymm0

进行256b操作
xorps
  • 3个融合域uop可以每0.75个周期发出一次,因为管道宽度是4个融合域uops。 (假设你用于B的寻址模式和结果可以微融合,否则它的5个融合域uops。)

  • 加载端口:SnB上的256b加载/存储需要2个周期(分成128b两半),但这会释放存储使用的端口2/3上的AGU。这是一个专用的存储数据端口,但存储地址计算需要来自加载端口的AGU。

    因此,只有128b或更小的加载/存储,SnB / IvB每个周期可以支持两个存储操作(其中至多一个是存储)。对于256b操作,SnB / IvB理论上可以承受两个256b负载和一个256b存储每两个周期。但是,缓存库冲突通常会使这种情况变得不可能。

    Haswell有一个专用的存储地址端口,可以支持两个256b负载和一个256b存储每个周期,并且没有缓存存储库冲突。因此,当所有内容都在L1缓存中时,Haswell会更快。

底线:理论上(没有缓存库冲突),这应该使SnB的加载和存储端口饱和,每个周期处理128b。每两个时钟需要一次Port5(唯一的端口VMOVDQU xmm0, [A] VMOVDQU xmm1, [A+16] VPXOR xmm0, xmm0, [B] VPXOR xmm1, xmm1, [B+16] VMOVDQU [result], xmm0 VMOVDQU [result+16], xmm1 可以运行)。

128b ops

VXORPS

这将成为地址生成的瓶颈,因为SnB每个周期只能维持两个128b内存操作。它还将在uop缓存中使用2倍的空间,以及更多的x86机器代码大小。除非存在缓存库冲突,否则这应该以每3个时钟256b-xor的吞吐量运行。

在寄存器

在寄存器之间,每个时钟有一个256b VPXOR和两个128b VPXOR会使SnB饱和。在Haswell上,每个时钟三个AVX2 256b XORPS将在每个周期中提供最多的XOR。 (PXORXORPS执行相同的操作,但VMOVDQU ymm0, [A] VMOVDQU ymm4, [B] VEXTRACTF128 xmm1, ymm0, 1 VEXTRACTF128 xmm5, ymm1, 1 VPXOR xmm0, xmm0, xmm4 VPXOR xmm1, xmm1, xmm5 VMOVDQU [res], xmm0 VMOVDQU [res+16], xmm1 的输出可以转发到FP执行单元而无需额外的转发延迟周期。我猜只有一个执行单元具有在FP域中具有XOR结果的布线so Intel CPUs post-Nehalem only run XORPS on one port。)

Z Boson的混合想法:

{{1}}

甚至更多融合域uops(8),而不仅仅是做128b-一切。

加载/存储:两个256b负载留下两个备用周期,用于生成两个存储地址,因此这仍然可以在两个负载/每个周期128b的一个存储中运行。

ALU:两个端口-5 uops(vextractf128),两个端口0/1/5 uop(vpxor)。

因此,这仍然具有每2个时钟一个256b结果的吞吐量,但它在3指令256b版本上的资源更多并且没有优势(在英特尔上)。 / p>

答案 1 :(得分:3)

使用_mm256_load_ps加载整数没有问题。事实上,在这种情况下,它比使用_mm256_load_si256(适用于AVX)更好,因为您使用_mm256_load_ps保留在浮点域中。

#include <x86intrin.h>
#include <stdio.h>

int main(void) {
    int a[8] = {1,2,3,4,5,6,7,8};
    int b[8] = {-2,-3,-4,-5,-6,-7,-8,-9};

    __m256 a8 = _mm256_loadu_ps((float*)a);
    __m256 b8 = _mm256_loadu_ps((float*)b);
    __m256 c8 = _mm256_xor_ps(a8,b8);
    int c[8]; _mm256_storeu_ps((float*)c, c8);
    printf("%x %x %x %x\n", c[0], c[1], c[2], c[3]);
}

如果你想留在你可以做的整数域

#include <x86intrin.h>
#include <stdio.h>

int main(void) {
    int a[8] = {1,2,3,4,5,6,7,8};
    int b[8] = {-2,-3,-4,-5,-6,-7,-8,-9};

    __m256i a8 = _mm256_loadu_si256((__m256i*)a);
    __m256i b8 = _mm256_loadu_si256((__m256i*)b);
    __m128i a8lo = _mm256_castsi256_si128(a8);
    __m128i a8hi = _mm256_extractf128_si256(a8, 1);
    __m128i b8lo = _mm256_castsi256_si128(b8);
    __m128i b8hi = _mm256_extractf128_si256(b8, 1);
    __m128i c8lo = _mm_xor_si128(a8lo, b8lo);
    __m128i c8hi = _mm_xor_si128(a8hi, b8hi);
    int c[8];
    _mm_storeu_si128((__m128i*)&c[0],c8lo);
    _mm_storeu_si128((__m128i*)&c[4],c8hi);
    printf("%x %x %x %x\n", c[0], c[1], c[2], c[3]);
}

_mm256_castsi256_si128内在函数是免费的。

答案 2 :(得分:1)

您可能会发现,与使用2 x _mm_xor_si128相比,性能上几乎没有差别。甚至有可能AVX实现会更慢,因为_mm256_xor_ps在SB / IB / Haswell上的倒数吞吐量为1,而_mm_xor_si128的倒数吞吐量为0.33。