我有一个不支持AVX2的AVX CPU,我想计算两个256位整数的按位异或。
由于_mm256_xor_si256
只适用于AVX2,我是否可以使用_mm256_load_ps
将这256位作为__m256
加载,然后执行_mm256_xor_ps
。这样会产生预期结果吗?
我的主要担忧是如果内存内容不是有效的浮点数,_mm256_load_ps
是否无法将位加载到寄存器中与内存中的位完全相同?
谢谢。
我有一个不支持AVX2的AVX CPU,我想计算两个256位整数的按位异或。
由于_mm256_xor_si256
只适用于AVX2,我是否可以使用_mm256_load_ps
将这256位作为__m256
加载,然后执行_mm256_xor_ps
。这样会产生预期结果吗?
我的主要担忧是如果内存内容不是有效的浮点数,_mm256_load_ps
是否无法将位加载到寄存器中与内存中的位完全相同?
谢谢。
以 64b 为单位存储到内存中,然后进行 128b 或 256b 的加载会 导致存储前传失败,增加几个时钟周期的延迟。使用 movq
/ pinsrq
将比 xor
更消耗执行资源。反之则不是那么糟糕:256b 存储 -> 64b 加载对于存储前传来说是没问题的。movq
/ pextrq
仍然很差,但延迟更低(代价是更多的 uops)。
VADDPS
SIMD浮点异常
溢出,下溢,无效, 精度,反规范化。
VMOVAPS
SIMD浮点异常
无。
(摘自英特尔指令参考手册。有关此手册及其他内容的链接,请参见x86 wiki页面。)
在英特尔硬件上,任何一种类型的加载/存储操作都可以在不额外延迟的情况下进入FP或整数域。AMD同样无论使用哪种类型的加载/存储操作,都会表现出相同的行为,不管数据去向/来源如何。
对于寄存器<-寄存器传递,使用不同类型的矢量移动指令实际上很重要。在Intel Nehalem上,使用错误的mov指令可能会导致旁路延迟。而在AMD Bulldozer系列中,由于移动是通过寄存器重命名而不是实际复制数据来处理的(与Intel IvB及更高版本类似),目标寄存器继承了写入源寄存器所在域的属性。
没有我阅读过的任何设计与movaps
不同地处理 movapd
。据推测,英特尔创建 movapd
的原因既是为了解码简单性,也是为了未来规划(例如允许存在双域和单域设计,具有不同的转发网络)。(movapd
就像每个其他 SSE 指令的双精度版本一样,只是添加了 66h
前缀字节。或者对于标量指令,使用 F2
而不是 F3
。)addps
的输出作为 addpd
的输入时产生了大延迟。但我不认为两个 addpd
指令之间甚至是 xorps
指令会导致该问题,只有实际的 FP 数学运算才会引起问题。(FP 位布尔运算对于 Bulldozer 家族而言是整数域。)
在只拥有AVX而没有AVX2的Intel SnB/IvB上的理论吞吐量:
xorps
进行256b操作VMOVDQU ymm0, [A]
VXORPS ymm0, ymm0, [B]
VMOVDQU [result], ymm0
由于流水线宽度为4个融合域uop,因此每0.75个周期可以发出3个融合域uop(假设您用于B和result的寻址模式可以微调,否则为5个融合域uop)。
加载端口:SnB上的256b加载/存储需要2个周期(分成128b的两半),但这会释放出端口2/3上的AGU以用于存储。其中有一个专门的存储数据端口,但存储地址计算需要来自加载端口的AGU。
因此,仅使用128b或更小的加载/存储,SnB/IvB可以每个周期维持两个内存操作(最多其中一个是存储)。对于256b的操作,SnB/IvB理论上可以每两个周期支持两个256b加载和一个256b存储。然而,缓存银行冲突通常使其不可能实现。
Haswell具有专用的存储地址端口,并且可以每个周期支持两个256b加载和一个256b存储。并且不会存在缓存银行冲突。所以,当所有东西都在L1高速缓存中时,Haswell要快得多。
xorps
指令的端口)每两个时钟周期需要使用一次。
VMOVDQU xmm0, [A]
VMOVDQU xmm1, [A+16]
VPXOR xmm0, xmm0, [B]
VPXOR xmm1, xmm1, [B+16]
VMOVDQU [result], xmm0
VMOVDQU [result+16], xmm1
在寄存器之间,每个时钟周期一个256b的VXORPS
和两个128b的VPXOR
会使SnB饱和。 在Haswell上,每个时钟周期三个AVX2 256b的VPXOR
将提供最多的异或操作。 (XORPS
和PXOR
执行相同的操作,但是XORPS
的输出可以直接转发到FP执行单元,而不需要额外的转发延迟周期。 我猜只有一个执行单元具有将XOR结果转换为FP域的布线,因此Intel Nehalem之后的CPU只在一个端口上运行XORPS。)
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
_mm_pshufb(...)
。 - Peter CordesB
yte、W
ord、D
word、Q
word这些东西从C语言中剔除,因为在32位/64位机器(如x86)上,“word”是16位,这会让人们感到困惑。 - Peter Cordes使用_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
内联函数是免费的。movdqa
,而不是只保留movaps
。 - Peter Cordes你可能会发现,使用 _mm256_xor_ps
和使用 2 x _mm_xor_si128
相比,性能几乎没有区别。甚至有可能 AVX 实现会更慢,因为在 SB/IB/Haswell 上,_mm256_xor_ps
的倒数吞吐量为1,而 _mm_xor_si128
的倒数吞吐量为0.33。
_mm_xor_si128
指令应该能够在同一时钟周期内执行,前提是没有其他依赖关系。(请参阅Agner Fog的"Instruction Tables"第3页,即Reciprocal Throughput)。 - Paul R