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

Sat*_*nan 21 c x86 simd avx avx2

内在的:

int mask = _mm256_movemask_epi8(__m256i s1)
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:我需要执行逆操作,因为内在函数_mm256_blendv_epi8只接受__m256i类型掩码而不是uint32_t.因此,在结果__m256i掩码中,我可以忽略除每个字节的MSB之外的位.

Sat*_*nan 14

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

#include <immintrin.h>
#include <boost/date_time/posix_time/posix_time.hpp>

//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;
}
Run Code Online (Sandbox Code Playgroud)

  • +1以跟进所有三个建议并提供结果的精彩摘要!出于兴趣,你使用了什么编译器? (3认同)
  • 谢谢!我同时使用了icc和g ++.我用优化标志更新了时序. (2认同)

Evg*_*uev 10

以下是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' 来完成,'1 '接下来的8个字节等
  3. VPOR (_mm256_or_si256)或选择每个字节的正确位VPAND (_mm256_and_si256).
  4. 使用适当的字节设置MSB VPCMPEQB (_mm256_cmpeq_epi8).比较每个字节0xFF.如果您希望屏蔽的每个位都切换,请使用VPAND上一步并比较为零.

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