是我想象还是SSE和AVX中缺少PNOT
指令?也就是说,缺少一个翻转向量中每个位的指令。
如果是的话,有比使用全1向量执行PXOR
更好的模拟方法吗?这很烦人,因为我需要设置一个全1向量来使用这种方法。
是我想象还是SSE和AVX中缺少PNOT
指令?也就是说,缺少一个翻转向量中每个位的指令。
如果是的话,有比使用全1向量执行PXOR
更好的模拟方法吗?这很烦人,因为我需要设置一个全1向量来使用这种方法。
对于这种情况,查看编译器会生成什么代码是很有指导意义的。
比如下面的函数:
#include <immintrin.h>
__m256i test(const __m256i v)
{
return ~v;
}
无论是gcc还是clang,似乎都会生成大致相同的代码:
test(long long __vector(4)):
vpcmpeqd ymm1, ymm1, ymm1
vpxor ymm0, ymm0, ymm1
ret
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
}
_mm256_set1_epi32(-1)
优化为 vpcmpeqd same,same
。我猜使用 AVX 技术,如果编译器本来不会生成这种指令,那么尝试“欺骗”它生成这种指令可能不会有太大影响。(在 SSE 中可能需要额外的 MOVDQA 指令,但是 AVX 的三操作数编码方式解决了这个问题。) - Peter Cordesvpternlogd
/ _mm512_ternarylogic_epi32(__m512i a, __m512i b, __m512i c, int imm8)
最终提供了一种实现NOT操作的方式,无需任何额外的常量,只需要使用单个指令即可在Skylake-avx512上的任何向量ALU端口运行。而且,使用AVX512VL,对于128位和256位向量也可以实现,而不会弄脏ZMM的上部。(除Xeon Phi之外的所有AVX512 CPU均具有AVX512VL)。vpternlogd zmm,zmm,zmm, imm8
有3个输入向量和一个输出,会直接修改目标寄存器。通过正确的立即数,您仍然可以在不同的寄存器中实现复制和NOT操作,但是它将对输出寄存器产生“虚假”依赖关系(而vpxord dst,src,all-ones
则不会)。
TL:DR:除非寄存器不够用,否则可能仍然使用带有全1的xor作为循环的一部分。如果需要后续使用其输入,则vpternlog
可能会增加一个vmovdqa
寄存器复制指令。
循环外,vpternlogd zmm,zmm,zmm, 0xff
是编译器创建512位全1向量的最佳选项, 因为AVX512比较指令将比较结果存储在掩码寄存器 (k0-k7
) 中,因此与全1异或可能已经涉及到了一个vpternlogd
,或者可能是从内存中广播常量,对于512位向量。或者是一个128位或256位vpcmpeqd same,same
的dep-breaking ALU uop。
imm[ (DEST[i]<<2) + (SRC1[i]<<1) + SRC2[i]]
,其中imm8
被视为一个8元素位图。zmm/m512/m32bcst
操作数),我们应选择一个重复1,0的位图,并在偶数位置(由src2=0
选择)上选择1
。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);
__m512i t2_inv = _mm512_ternarylogic_epi32(t2, t2, t2, 0b01010101);
vmovdqa32
上浪费一些前端吞吐量。此外,AVX512VL指令的128位和256位版本通常是3/clock,包括vpternlogd ymm
。如果您只使用256位向量来避免整个程序中某个小部分的turbo惩罚,您仍然可以使用vpternlogd ymm
。此外,Gold上的FP FMA/add/mul每个时钟周期可以运行2次,所以我不知道您所说的“只有少数”指令在512位向量上以2/clock运行是什么意思。 - Peter CordesPANDN
操作码进行此操作。
PANDN
实现了该操作。DEST = NOT(DEST) AND SRC ; (SSEx)
或者
DEST = NOT(SRC1) AND SRC2 ; (AVXx)
将这个操作与全1向量结合,有效地得到一个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)
这两个内容(当然)都可以很容易地转换为内部函数。
1
的向量并不特别困难:[v]pcmpe[typesize] %[x/y]mmN, %[x/y]mmN[, %[x/y]mmN]
或类似的指令。单个指令来设置常量似乎并不太繁琐。如果您特别反感xor
,则pandn
和andnps
也可用。 - EOFPNEG
指令在哪里? - Joost