如何在 SSE/AVX 中执行按位非?
How do I perform a bitwise NOT in SSE/AVX?
是我的错觉,还是SSE和AVX少了一条PNOT
指令?即,翻转向量中每一位的指令。
如果是,是否有比 PXOR
用全 1 向量模拟它更好的方法?非常烦人,因为我需要设置全 1 的向量才能使用该方法。
如果你使用 Intrinsics,你可以使用像这样的内联函数来单独进行 not 操作。
inline __m256i _mm256_not_si256 (__m256i a){
//return _mm256_xor_si256 (a, _mm256_set1_epi32(0xffffffff));
return _mm256_xor_si256 (a, _mm256_cmpeq_epi32(a,a));//I didn't check wich one is faster
}
对于这种情况,查看编译器生成的内容可能很有启发性。
例如对于以下功能:
#include <immintrin.h>
__m256i test(const __m256i v)
{
return ~v;
}
gcc 和 clang 似乎都 generate much the same code:
test(long long __vector(4)):
vpcmpeqd ymm1, ymm1, ymm1
vpxor ymm0, ymm0, ymm1
ret
您可以为此使用 PANDN
OpCode。
PANDN
执行操作
DEST = NOT(DEST) AND SRC ; (SSEx)
或
DEST = NOT(SRC1) AND SRC2 ; (AVXx)
将此操作与全一向量有效地结合起来会产生 PNOT 操作。
一些 x86(SSEx) 汇编代码如下所示:
; XMM0 is input register
PCMPEQB xmm1, xmm1 ; Whole xmm1 reg set to 1's
PANDN xmm0, xmm1 ; xmm0 = NOT(xmm0) AND xmm1
; XMM0 contains NOT(XMM0)
一些 x86(AVXx) 汇编代码如下所示:
; YMM0 is input register
VPCMPEQB ymm1, ymm1, ymm1 ; Whole ymm1 reg set to 1's
VPANDN ymm0, ymm0, ymm1 ; ymm0 = NOT(ymm0) AND ymm1
; YMM0 contains NOT(YMM0)
两者(当然)都可以很容易地转换为内在函数。
AVX512F vpternlogd
/ _mm512_ternarylogic_epi32(__m512i a, __m512i b, __m512i c, int imm8)
最终提供了一种无需任何额外常量即可实现 NOT 的方法,使用一条指令可以 运行 在 Skylake-avx512 上的任何向量 ALU 端口上。
对于 AVX512VL,128 位和 256 位向量也不会弄脏 ZMM 的上半部分。 (除了 Xeon Phi 之外的所有 AVX512 CPU 都有 AVX512VL)。
在 Intel CPU 上,它可以 运行 在任何端口 0、1 或 5 上,因此 128 和 256 位版本的吞吐量为 3/clock。或者像往常一样,512 位向量为 2/clock,因为当任何 512 位微指令运行时端口 1 都会关闭。
https://www.uops.info/html-instr/VPTERNLOGD_XMM_XMM_XMM_I8.html).
vpternlogd zmm,zmm,zmm, imm8
有 3 个输入向量和一个输出向量,就地修改目标。使用正确的立即数,您仍然可以在不同的寄存器中实现“复制与非”,但是它将对输出寄存器(vpxord dst, src, all-ones
具有“错误”依赖性不会)。
TL:DR: 可能仍将异或与全一一起用作循环的一部分,除非您 运行 寄存器用完了。如果稍后需要输入,vpternlog
可能会花费额外的 vmovdqa
寄存器复制指令。
在循环外,vpternlogd zmm,zmm,zmm, 0xff
是 ,因为 AVX512 比较指令比较到掩码 (k0-k7
),所以与全一的 XOR 可能已经涉及 vpternlogd
,或者可能是来自内存的广播常量,用于 512 位向量。或者 128 或 256 位 vpcmpeqd same,same
.
的 dep-breaking ALU uop
对于每个位位置 i
,输出位为 imm[ (DEST[i]<<2) + (SRC1[i]<<1) + SRC2[i]]
,其中 imm8
被视为 8 元素位图。
因此,如果我们希望结果只依赖于 SRC2(即 zmm/m512/m32bcst
操作数),我们应该选择重复 1,0 的位图,1
在偶数位置(由 src2=0
选择)。
vpternlogd zmm1,zmm1, zmm2, 01010101b ; 0x55 ; false dep on zmm1
如果幸运的话,如果有利可图,编译器会为您优化 _mm512_xor_epi32(v, _mm512_set1_epi32(-1))
到 vpternlogd
。
// To hand-hold a compiler into saving a vmovdqa32 if needed:
__m512i tmp = something earlier;
__m512i t2 = _mm...(tmp);
// use-case: tmp is dead, t2 and ~t2 are both needed.
__m512i t2_inv = _mm512_ternarylogic_epi32(tmp, t2, t2, 0b01010101);
如果您不确定这是个好主意,请保持简单并为所有 3 个输入使用相同的变量:
__m512i t2_inv = _mm512_ternarylogic_epi32(t2, t2, t2, 0b01010101);
是我的错觉,还是SSE和AVX少了一条PNOT
指令?即,翻转向量中每一位的指令。
如果是,是否有比 PXOR
用全 1 向量模拟它更好的方法?非常烦人,因为我需要设置全 1 的向量才能使用该方法。
如果你使用 Intrinsics,你可以使用像这样的内联函数来单独进行 not 操作。
inline __m256i _mm256_not_si256 (__m256i a){
//return _mm256_xor_si256 (a, _mm256_set1_epi32(0xffffffff));
return _mm256_xor_si256 (a, _mm256_cmpeq_epi32(a,a));//I didn't check wich one is faster
}
对于这种情况,查看编译器生成的内容可能很有启发性。
例如对于以下功能:
#include <immintrin.h>
__m256i test(const __m256i v)
{
return ~v;
}
gcc 和 clang 似乎都 generate much the same code:
test(long long __vector(4)):
vpcmpeqd ymm1, ymm1, ymm1
vpxor ymm0, ymm0, ymm1
ret
您可以为此使用 PANDN
OpCode。
PANDN
执行操作
DEST = NOT(DEST) AND SRC ; (SSEx)
或
DEST = NOT(SRC1) AND SRC2 ; (AVXx)
将此操作与全一向量有效地结合起来会产生 PNOT 操作。
一些 x86(SSEx) 汇编代码如下所示:
; XMM0 is input register
PCMPEQB xmm1, xmm1 ; Whole xmm1 reg set to 1's
PANDN xmm0, xmm1 ; xmm0 = NOT(xmm0) AND xmm1
; XMM0 contains NOT(XMM0)
一些 x86(AVXx) 汇编代码如下所示:
; YMM0 is input register
VPCMPEQB ymm1, ymm1, ymm1 ; Whole ymm1 reg set to 1's
VPANDN ymm0, ymm0, ymm1 ; ymm0 = NOT(ymm0) AND ymm1
; YMM0 contains NOT(YMM0)
两者(当然)都可以很容易地转换为内在函数。
AVX512F vpternlogd
/ _mm512_ternarylogic_epi32(__m512i a, __m512i b, __m512i c, int imm8)
最终提供了一种无需任何额外常量即可实现 NOT 的方法,使用一条指令可以 运行 在 Skylake-avx512 上的任何向量 ALU 端口上。
对于 AVX512VL,128 位和 256 位向量也不会弄脏 ZMM 的上半部分。 (除了 Xeon Phi 之外的所有 AVX512 CPU 都有 AVX512VL)。
在 Intel CPU 上,它可以 运行 在任何端口 0、1 或 5 上,因此 128 和 256 位版本的吞吐量为 3/clock。或者像往常一样,512 位向量为 2/clock,因为当任何 512 位微指令运行时端口 1 都会关闭。 https://www.uops.info/html-instr/VPTERNLOGD_XMM_XMM_XMM_I8.html).
vpternlogd zmm,zmm,zmm, imm8
有 3 个输入向量和一个输出向量,就地修改目标。使用正确的立即数,您仍然可以在不同的寄存器中实现“复制与非”,但是它将对输出寄存器(vpxord dst, src, all-ones
具有“错误”依赖性不会)。
TL:DR: 可能仍将异或与全一一起用作循环的一部分,除非您 运行 寄存器用完了。如果稍后需要输入,vpternlog
可能会花费额外的 vmovdqa
寄存器复制指令。
在循环外,vpternlogd zmm,zmm,zmm, 0xff
是 k0-k7
),所以与全一的 XOR 可能已经涉及 vpternlogd
,或者可能是来自内存的广播常量,用于 512 位向量。或者 128 或 256 位 vpcmpeqd same,same
.
对于每个位位置 i
,输出位为 imm[ (DEST[i]<<2) + (SRC1[i]<<1) + SRC2[i]]
,其中 imm8
被视为 8 元素位图。
因此,如果我们希望结果只依赖于 SRC2(即 zmm/m512/m32bcst
操作数),我们应该选择重复 1,0 的位图,1
在偶数位置(由 src2=0
选择)。
vpternlogd zmm1,zmm1, zmm2, 01010101b ; 0x55 ; false dep on zmm1
如果幸运的话,如果有利可图,编译器会为您优化 _mm512_xor_epi32(v, _mm512_set1_epi32(-1))
到 vpternlogd
。
// To hand-hold a compiler into saving a vmovdqa32 if needed:
__m512i tmp = something earlier;
__m512i t2 = _mm...(tmp);
// use-case: tmp is dead, t2 and ~t2 are both needed.
__m512i t2_inv = _mm512_ternarylogic_epi32(tmp, t2, t2, 0b01010101);
如果您不确定这是个好主意,请保持简单并为所有 3 个输入使用相同的变量:
__m512i t2_inv = _mm512_ternarylogic_epi32(t2, t2, t2, 0b01010101);