这是我的想象,还是一个PNOT
指令缺少SSE和AVX?也就是说,翻转矢量中每一位的指令。是不是从SSE,AVX失踪?
如果是,是否有更好的方法来模拟它,而不是PXOR
与所有1的向量?很烦人,因为我需要建立一个全1的矢量来使用这种方法。
这是我的想象,还是一个PNOT
指令缺少SSE和AVX?也就是说,翻转矢量中每一位的指令。是不是从SSE,AVX失踪?
如果是,是否有更好的方法来模拟它,而不是PXOR
与所有1的向量?很烦人,因为我需要建立一个全1的矢量来使用这种方法。
对于这样的情况,看看编译器会产生什么是有益的。
E.g.以下功能:
#include <immintrin.h>
__m256i test(const __m256i v)
{
return ~v;
}
GCC和铛似乎generate much the same code:
test(long long __vector(4)):
vpcmpeqd ymm1, ymm1, ymm1
vpxor ymm0, ymm0, ymm1
ret
如果使用Intrinsics,则可以使用这样的内联函数分别执行非操作。
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 3操作数编码解决了这个问题。) –
可以使用PANDN
操作码为。
PANDN
实现操作
DEST = NOT(DEST) AND SRC ; (SSEx)
或
DEST = NOT(SRC1) AND SRC2 ; (AVXx)
结合此操作用全1矢量有效地导致一个PNOT操作。
一些86(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)
一些86(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)
终于提供了一个方法来实现没有任何额外的常量,使用单个指令(其中有2个,每个时钟吞吐量Skylake-avx512和KNL,所以它不太好,因为PXOR/XORPS为256B和小)
vpternlogd zmm,zmm,zmm, imm8
有3个输入向量和一个输出,修改目的地。如果立即执行正确的操作,您仍然可以将一个复制和不复制到一个不同的寄存器中,但是它将对输出寄存器有一个“错误”的依赖关系(vpxord dst, src, all-ones
不会)。
TL:DR:DR可能仍然使用xor作为循环的一部分,除非您的注册表不足。如果稍后需要其输入,则可能需要花费额外的vmovdqa
寄存器复制指令。在循环之外,vpternlogd zmm,zmm,zmm, 0xff
为the compiler's best option for creating a 512b vector of all-ones in the first place,因为AVX512比较指令比较为掩码(k0-k7
),所以与全1的异或可能涉及vpternlogd
,或者可能是内存中的广播常量。
对于每个比特位置i
,输出位是imm[ (DEST[i]<<2) + (SRC1[i]<<1) + SRC2[i]]
,其中imm8
被视为一个8单元的位图。
因此,如果我们想要的结果就只有SRC2依赖(这是zmm/m512/m32bcst
操作数),我们应该选择在偶数位置(由src2=0
选择)重复1,0,与1
的位图。
vpternlogd zmm1, zmm2,zmm2, 01010101b ; 0x55
如果你幸运的话,编译器将优化_mm512_xor_epi32(v, set1(-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);
设置所有的向量'1's不特别困难:'[v] pcmpe [typesize] \t%[x/y] mmN,%[x/y] mmN [,%[x/y] mmN]'或大约。建立常数的单个指令看起来不太繁重。如果你对'xor'有特殊的反感,'pandn'和'andnps'也是可用的。 – EOF
这并不可怕 - 但是,只要我期待这样的基本操作,它就是2倍。当然,这个常数可能会以登记册为代价而被吊起来。无论如何,只是检查我的假设,我没有错过这个地方。 @EOF – SODIMM
鉴于'pcmpeXX'因为至少是Sandy Bridge(根据Agner Fog的微体系结构手册)而被认为是依赖打破的,所以在几乎所有情况下,无论是否需要一条或两条指令来否定向量都无关紧要。 – EOF