【问题标题】:Combine packed nibbles into packed bytes将打包的半字节组合成打包的字节
【发布时间】:2017-09-09 20:17:27
【问题描述】:

给定一个或多个 __m128i__m256i 每个 16 位元素包含一个半字节,将它们组合并打包成每个 8 位元素一个字节的最快方法是什么(即 (hi << 4) | lo 用于相邻的 16 位元素元素)?

这是我想出的最好方法,不幸的是可以与标量代码相媲美:

const static __m256i shufmask = _mm256_setr_epi8(
  2, 255, 255, 255, 6, 255, 255, 255, 10, 255, 255, 255, 14, 255, 255, 255,
  2, 255, 255, 255, 6, 255, 255, 255, 10, 255, 255, 255, 14, 255, 255, 255);

const static __m256i high4 = _mm256_setr_epi8(
  255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0,
  255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0
);

inline static int64_t hnib2byte(__m256i nibbles) { // (a << 4) | b;
  // hi 0 lo 0, ...
  __m256i upper = _mm256_slli_epi16(nibbles, 4);

  // Align upper and lower halves so they can be ORed vertically
  // lo 0 0 0, ...
  __m256i lower = _mm256_shuffle_epi8(nibbles, shufmask);

  // ab x x x, ...
  __m256i or = _mm256_or_si256(upper, lower);

  // Pack into bytes
  or = _mm256_and_si256(or, high4);
  __m256i pack16 = _mm256_packus_epi16(or, or);
  const int _3to2 = 0b00001000;
  __m256i perm16 = _mm256_permute4x64_epi64(pack16, _3to2); // :(
  __m256i pack8 = _mm256_packus_epi16(perm16, perm16);

  return _mm_cvtsi128_si64(_mm256_castsi256_si128(pack8));
}

直到 AVX2 和包括在内的指令都是公平的游戏。 AVX-512 中的掩蔽移位开辟了更好的选择。这是在循环中调用的,因此在早期将半字节打包成 8 位元素也是公平的游戏。

【问题讨论】:

    标签: c++ x86 simd


    【解决方案1】:

    下面的解决方案hnib2byte_v2 应该比您的解决方案快一点,至少在英特尔处理器上是这样。

    指令 vpermd 或内在 _mm256_permutevar8x32_epi32 在 AMD Ryzen 上运行缓慢。在该平台上最好使用_mm256_extracti128_si256 提取pck 的上128 位通道,使用_mm256_castsi256_si128 提取下128 位通道,并将这两者与_mm256_or_si256 结合以获得以最低 64 位回答。

    /*
    gcc -O3 -m64 -Wall -mavx2 -march=broadwell nibble2byte.c
    */
    #include <immintrin.h>
    #include <stdio.h>
    #include <stdint.h>
    
    int print_avx2_hex(__m256i ymm);
    
    
    inline static int64_t hnib2byte_v2(__m256i nibbles) {
      __m256i shufmask8  = _mm256_set_epi8(-1,-1,-1,-1,  -1,-1,-1,-1,  14,10,6,2,  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,  -1,-1,-1,-1,  14,10,6,2);
      __m256i shufmask32 = _mm256_set_epi32(7,7,7,7,7,7,5,0);
    
      __m256i lower      = _mm256_slli_epi32(nibbles, 20);
    // 00E0000000C00000 00A0000000800000 0060000000400000 0020000000000000
    
      __m256i up_lo      = _mm256_or_si256(lower,nibbles);
    // 00EF000E00CD000C 00AB000A00890008 0067000600450004 0023000200010000
    
      __m256i pck        = _mm256_shuffle_epi8(up_lo,shufmask8);
    // 0000000000000000 EFCDAB8900000000 0000000000000000 0000000067452301
    
      __m256i pck64      = _mm256_permutevar8x32_epi32(pck,shufmask32);
    // 0000000000000000 0000000000000000 0000000000000000 EFCDAB8967452301
    
    //  print_avx2_hex(lower);
    //  print_avx2_hex(up_lo);
    //  print_avx2_hex(pck);
    //  print_avx2_hex(pck64);
    
      return _mm_cvtsi128_si64(_mm256_castsi256_si128(pck64));
    }
    
    
    inline static int64_t hnib2byte(__m256i nibbles) { // (a << 4) | b;
    
    __m256i shufmask = _mm256_setr_epi8(
      2, 255, 255, 255, 6, 255, 255, 255, 10, 255, 255, 255, 14, 255, 255, 255,
      2, 255, 255, 255, 6, 255, 255, 255, 10, 255, 255, 255, 14, 255, 255, 255);
    
    __m256i high4 = _mm256_setr_epi8(
      255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0,
      255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0);
    
      // hi 0 lo 0, ...
      __m256i upper = _mm256_slli_epi16(nibbles, 4);
    
      // Align upper and lower halves so they can be ORed vertically
      // lo 0 0 0, ...
      __m256i lower = _mm256_shuffle_epi8(nibbles, shufmask);
    
      // ab x x x, ...
      __m256i or = _mm256_or_si256(upper, lower);
    
      // Pack into bytes
      or = _mm256_and_si256(or, high4);
      __m256i pack16 = _mm256_packus_epi16(or, or);
      const int _3to2 = 0b00001000;
      __m256i perm16 = _mm256_permute4x64_epi64(pack16, _3to2); // :(
      __m256i pack8 = _mm256_packus_epi16(perm16, perm16);
    
      return _mm_cvtsi128_si64(_mm256_castsi256_si128(pack8));
    }
    
    
    int print_avx2_hex(__m256i ymm)
    {
        long unsigned int x[4];
            _mm256_storeu_si256((__m256i*)x,ymm);
            printf("%016lX %016lX %016lX %016lX\n", x[3],x[2],x[1],x[0]);
    
        return 0;
    }
    
    
    int main()
    {
       uint64_t x;
        __m256i nibble_x16 = _mm256_set_epi16(0x000F,0x000E,0x000D,0x000C,  0x000B,0x000A,0x0009,0x0008,  
                                              0x0007,0x0006,0x0005,0x0004,  0x0003,0x0002,0x0001,0x0000);
        printf("AVX variable: \n");
        print_avx2_hex(nibble_x16);                                      
        x = hnib2byte(nibble_x16);
        printf("With hnib2byte    x = %016lX \n\n",x);
    
        printf("AVX variable: \n");
        print_avx2_hex(nibble_x16);                                      
        x = hnib2byte_v2(nibble_x16);
        printf("With hnib2byte_v2 x = %016lX \n",x);
        return 0;
    }
    

    输出是:

    $ ./a.out
    AVX variable: 
    000F000E000D000C 000B000A00090008 0007000600050004 0003000200010000
    With hnib2byte    x = EFCDAB8967452301 
    
    AVX variable: 
    000F000E000D000C 000B000A00090008 0007000600050004 0003000200010000
    With hnib2byte_v2 x = EFCDAB8967452301 
    

    两种方法的输出与此处选择的输入相等。

    除了加载 shuffle 常量(应该在循环外完成)之外,它只编译为五个指令: vpslld,vpor,vpshufb,vpermd,和vmovq,比你的解决方案少三个。

    【讨论】:

    • 哦对了,不用加的时候pshufb比几个packus简单!谢谢。
    猜你喜欢
    • 1970-01-01
    • 2013-01-07
    • 1970-01-01
    • 2014-10-27
    • 2015-07-02
    • 1970-01-01
    • 2017-05-30
    • 2011-06-16
    • 1970-01-01
    相关资源
    最近更新 更多