如何执行_mm256_movemask_epi8(VPMOVMSKB)的反转?

内在的:

int mask = _mm256_movemask_epi8(__m256i s1) 

创建一个掩码,其32位对应于s1的每个字节的最高位。 在使用位操作(例如BMI2 )操作掩码之后,我想执行_mm256_movemask_epi8的反转,即创建__m256i向量,其中每个字节的最高有效位包含uint32_t mask的相应位。

做这个的最好方式是什么?

编辑:我需要执行逆操作,因为内部_mm256_blendv_epi8只接受__m256i类型的掩码而不是uint32_t 。 因此,在生成的__m256i掩码中,我可以忽略每个字节的MSB以外的位。

以下是可能更有效的LUT或pdep指令的替代方法:

  1. 将32位掩码复制到某个ymm寄存器的低字节和同一寄存器的字节16..19。 您可以使用临时数组和_mm256_load_si256 。 或者您可以将32位掩码的单个副本移动到某个ymm寄存器的低字节,然后使用VPBROADCASTD (_mm_broadcastd_epi32)或其他广播/随机指令进行广播。
  2. 重新排列寄存器的字节,使低8字节(每个)包含掩码的低8位,接下来的8个字节 – 接下来的8位等。这可以用VPSHUFB (_mm256_shuffle_epi8) ,控制寄存器在低8时包含’0’字节,接下来的8个字节中的’1’等。
  3. 使用VPOR (_mm256_or_si256)VPAND (_mm256_and_si256)为每个字节选择适当的位。
  4. 使用VPCMPEQB (_mm256_cmpeq_epi8)设置适当字节的MSB。 将每个字节与0xFF进行比较。 如果您希望屏蔽的每个位都切换, VPAND在上一步使用VPAND并比较为零。

这种方法的另一个灵活性是你可以为步骤#2选择不同的控制寄存器,为步骤#3选择不同的掩码来改变位掩码的位(例如,你可以将这个掩码以相反的顺序复制到ymm寄存器)。

我在Haswell机器上实现了上述三种方法。 Evgeny Kluev的方法是最快的(1.07秒),其次是Jason R(1.97秒)和Paul R(2.44秒)。 下面的代码是使用-march = core-avx2 -O3优化标志编译的。

 #include  #include  //t_icc = 1.07 s //t_g++ = 1.09 s __m256i get_mask3(const uint32_t mask) { __m256i vmask(_mm256_set1_epi32(mask)); const __m256i shuffle(_mm256_setr_epi64x(0x0000000000000000, 0x0101010101010101, 0x0202020202020202, 0x0303030303030303)); vmask = _mm256_shuffle_epi8(vmask, shuffle); const __m256i bit_mask(_mm256_set1_epi64x(0x7fbfdfeff7fbfdfe)); vmask = _mm256_or_si256(vmask, bit_mask); return _mm256_cmpeq_epi8(vmask, _mm256_set1_epi64x(-1)); } //t_icc = 1.97 s //t_g++ = 1.97 s __m256i get_mask2(const uint32_t mask) { __m256i vmask(_mm256_set1_epi32(mask)); const __m256i shift(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0)); vmask = _mm256_sllv_epi32(vmask, shift); const __m256i shuffle(_mm256_setr_epi64x(0x0105090d0004080c, 0x03070b0f02060a0e, 0x0105090d0004080c, 0x03070b0f02060a0e)); vmask = _mm256_shuffle_epi8(vmask, shuffle); const __m256i perm(_mm256_setr_epi64x(0x0000000000000004, 0x0000000100000005, 0x0000000200000006, 0x0000000300000007)); return _mm256_permutevar8x32_epi32(vmask, perm); } //t_icc = 2.44 s //t_g++ = 2.45 s __m256i get_mask1(uint32_t mask) { const uint64_t pmask = 0x8080808080808080ULL; // bit unpacking mask for PDEP uint64_t amask0, amask1, amask2, amask3; amask0 = _pdep_u64(mask, pmask); mask >>= 8; amask1 = _pdep_u64(mask, pmask); mask >>= 8; amask2 = _pdep_u64(mask, pmask); mask >>= 8; amask3 = _pdep_u64(mask, pmask); return _mm256_set_epi64x(amask3, amask2, amask1, amask0); } int main() { __m256i mask; boost::posix_time::ptime start( boost::posix_time::microsec_clock::universal_time()); for(unsigned i(0); i != 1000000000; ++i) { mask = _mm256_xor_si256(mask, get_mask3(i)); } boost::posix_time::ptime end( boost::posix_time::microsec_clock::universal_time()); std::cout << "duration:" << (end-start) << " mask:" << _mm256_movemask_epi8(mask) << std::endl; return 0; } 

这是另一个可能适用于AVX2的实现,因为你的问题上有这个标签(因为我没有Haswell机器,所以它没有经过测试)。 它类似于Evgeny Kluev的答案,但它可能需要更少的指令。 但是,它需要两个不变的__m256i掩模。 如果你在一个循环中多次这样做,那么提前设置这些常量的开销可能是微不足道的。

  • 使用32位掩码,使用_mm_broadcastd_epi32()其广播到ymm寄存器的所有8个插槽。

  • 创建一个__m256i 8个32位整数,其值为[0, 1, 2, 3, 4, 5, 6, 7] (从最低有效元素到最高有效元素)。

  • 使用常量掩码使用_mm256_sllv_epi32()ymm寄存器中的每个32位整数旋转不同的量。

  • 现在,如果我们将ymm寄存器视为保持8位整数并查看它们的MSB,那么寄存器现在保存字节索引的MSB [7, 15, 23, 31, 6, 14, 22, 30, 5, 13, 21, 29, 4, 12, 20, 28, 3, 11, 19, 27, 2, 10, 18, 26, 1, 9, 17, 25, 0, 8, 16, 24] ymm [7, 15, 23, 31, 6, 14, 22, 30, 5, 13, 21, 29, 4, 12, 20, 28, 3, 11, 19, 27, 2, 10, 18, 26, 1, 9, 17, 25, 0, 8, 16, 24] 21,29,4,12,20,28,3,11,19,27,2,10,18,26,1,9,17,25,0,8,16,24 [7, 15, 23, 31, 6, 14, 22, 30, 5, 13, 21, 29, 4, 12, 20, 28, 3, 11, 19, 27, 2, 10, 18, 26, 1, 9, 17, 25, 0, 8, 16, 24] (起源于重要的是最重要的元素)。

  • [0x80, 0x80, 0x80, ...]的常量掩码使用按位AND [0x80, 0x80, 0x80, ...]以隔离每个字节的MSB。

  • 使用一系列shuffle和/或permute以按照您想要的顺序返回元素。 不幸的是,对于8位整数没有任何任何置换,就像AVX2中的浮点值一样。

我对此的初步处理类似于@Jason R,因为这就是“正常”操作的工作原理,但大多数操作只关心高位 – 忽略所有其他位。 一旦我意识到这一点, _mm*_maskz_broadcast*_epi*(mask,__m128i)系列函数最有意义。 您需要启用-mavx512vl和-mavx512bw(gcc)

要根据掩码获取每个字节集的最高位的向量:

 /* convert 16 bit mask to __m128i control byte mask */ _mm_maskz_broadcastb_epi8((__mmask16)mask,_mm_set1_epi32(~0)) /* convert 32 bit mask to __m256i control byte mask */ _mm256_maskz_broadcastb_epi8((__mmask32)mask,_mm_set1_epi32(~0)) /* convert 64 bit mask to __m512i control byte mask */ _mm512_maskz_broadcastb_epi8((__mmask64)mask,_mm_set1_epi32(~0)) 

根据掩码获得具有每个集最高位的向量:

 /* convert 8 bit mask to __m128i control word mask */ _mm_maskz_broadcastw_epi16((__mmask8)mask,_mm_set1_epi32(~0)) /* convert 16 bit mask to __m256i control word mask */ _mm256_maskz_broadcastw_epi16((__mmask16)mask,_mm_set1_epi32(~0)) /* convert 32 bit mask to __m512i control word mask */ _mm512_maskz_broadcastw_epi16((__mmask32)mask,_mm_set1_epi32(~0)) 

根据掩码获得具有每个双字组最高位的向量:

 /* convert 8 bit mask to __m256i control mask */ _mm256_maskz_broadcastd_epi32((__mmask8)mask,_mm_set1_epi32(~0)) /* convert 16 bit mask to __m512i control mask */ _mm512_maskz_broadcastd_epi32((__mmask16)mask,_mm_set1_epi32(~0)) 

根据掩码获得具有每个四字集最高位的向量:

 /* convert 8 bit mask to __m512i control mask */ _mm512_maskz_broadcastq_epi64((__mmask8)mask,_mm_set1_epi32(~0)) 

这个问题的具体问题是: _mm256_maskz_broadcastb_epi8((__mmask32)mask,_mm_set1_epi32(~0))但我包含其他参考/比较。

请注意,每个字节/字/ …将根据掩码(不仅是最高位)全部为1或全为零。 这对于进行矢量化位操作(例如,使用另一个矢量来消除不需要的字节/字)也很有用。

另一个注意事项:每个_mm_set1_epi32(~0)可以/应该转换为常量(手动或通过编译器),因此它应该编译为一个相当快速的操作,尽管它在测试中可能比现实生活中稍快一些常数可能会留在寄存器中。 然后将它们转换为VPMOVM2 {b,w,d,q}指令

编辑:如果您的编译器不支持AVX512,内联汇编版本应如下所示:

 inline __m256i dmask2epi8(__mmask32 mask){ __m256i ret; __asm("vpmovm2b %1, %0":"=x"(ret):"k"(mask):); return ret; } 

其他说明是相似的。

我能想到的唯一合理有效的方法是使用8位LUT:执行4 x 8位查找,然后将结果加载到矢量中,例如

 static const uint64_t LUT[256] = { 0x0000000000000000ULL, ... 0xffffffffffffffffULL }; uint64_t amask[4] __attribute__ ((aligned(32))); uint32_t mask; __m256i vmask; amask[0] = LUT[mask & 0xff]; amask[1] = LUT[(mask >> 8) & 0xff]; amask[2] = LUT[(mask >> 16) & 0xff]; amask[3] = LUT[mask >> 24]; vmask = _mm256_load_si256((__m256i *)amask); 

或者,您可以使用寄存器而不是临时数组,看看您的编译器是否可以执行更高效的操作,而不涉及通过内存:

 static const uint64_t LUT[256] = { 0x0000000000000000ULL, ... 0xffffffffffffffffULL }; uint64_t amask0, amask1, amask2, amask3; uint32_t mask; __m256i vmask; amask0 = LUT[mask & 0xff]; amask1 = LUT[(mask >> 8) & 0xff]; amask2 = LUT[(mask >> 16) & 0xff]; amask3 = LUT[mask >> 24]; vmask = _mm256_set_epi64x(amask3, amask2, amask1, amask0); 

事后的想法:一个有趣的挑战可能是使用例如Haswell BMI指令来执行相当于8 – > 64位LUT操作,从而摆脱LUT。 看起来你可以使用PDEP ,例如

 const uint64_t pmask = 0x8080808080808080ULL; // bit unpacking mask for PDEP uint64_t amask0, amask1, amask2, amask3; uint32_t mask; __m256i vmask; amask0 = _pdep_u64(mask, pmask); mask >>= 8; amask1 = _pdep_u64(mask, pmask); mask >>= 8; amask2 = _pdep_u64(mask, pmask); mask >>= 8; amask3 = _pdep_u64(mask, pmask); vmask = _mm256_set_epi64x(amask3, amask2, amask1, amask0);