【问题标题】:Setting last or first n bits in SSE register设置 SSE 寄存器的最后或前 n 位
【发布时间】:2014-05-13 10:23:55
【问题描述】:

如何创建一个设置了n 最高有效位的__m128i(在整个向量中)?我需要它来掩盖与计算相关的缓冲区部分。如果可能,解决方案应该没有分支,但这似乎很难实现

我该怎么做?

【问题讨论】:

  • 你是指整个128位向量中的n个MS位,还是向量的每个元素中的n个MS位?
  • 问题已更新。在整个向量中
  • 谢谢 - 大概 n 可以取任何值,即它不是一些方便的值,比如 8 的倍数?
  • 不,不幸的是n 可以是任意的 (1

标签: c++ x86 sse simd intrinsics


【解决方案1】:

我将此添加为第二个答案,并将第一个答案留作历史兴趣。看起来你可以用_mm_slli_epi64做一些更高效的事情:

#include <emmintrin.h>
#include <stdio.h>

__m128i bit_mask(int n)
{
    __m128i v0 = _mm_set_epi64x(-1, -(n > 64)); // AND mask
    __m128i v1 = _mm_set_epi64x(-(n > 64), 0);  // OR mask
    __m128i v2 = _mm_slli_epi64(_mm_set1_epi64x(-1), (128 - n) & 63);
    v2 = _mm_and_si128(v2, v0);
    v2 = _mm_or_si128(v2, v1);
    return v2;
}

int main(int argc, char *argv[])
{
    int n = 36;

    if (argc > 1) n = atoi(argv[1]);

    printf("bit_mask(%3d) = %02vx\n", n, bit_mask(n));

    return 0;
}

测试:

$ gcc -Wall -msse2 sse_bit_mask.c
$ for n in 1 2 3 63 64 65 127 128 ; do ./a.out $n ; done
bit_mask(  1) = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80
bit_mask(  2) = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0
bit_mask(  3) = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e0
bit_mask( 63) = 00 00 00 00 00 00 00 00 fe ff ff ff ff ff ff ff
bit_mask( 64) = 00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff
bit_mask( 65) = 00 00 00 00 00 00 00 80 ff ff ff ff ff ff ff ff
bit_mask(127) = fe ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
bit_mask(128) = ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

【讨论】:

  • 这看起来不太好 - 65127 的结果不正确;我错过了什么吗?
  • 很抱歉 - 我现在意识到我需要两个面具,而不是一个。它现在似乎工作正常。
  • @PaulR,printf 中的 %vx 选项是什么?这打印 SSE 寄存器?哪个编译器? coliru.stacked-crooked.com/a/b8fd938a10e98b17
  • 在某些编译器中支持作为扩展,例如Apple 在 OS X 上构建的 gcc 和 clang(我主要用于开发工作)
  • @PaulR 所以我想这在 linux 上不起作用......我想出了一个解决方案,使用广播将所有设置为一个,并结合按位移位
【解决方案2】:

您可以使用this question 中的一种方法生成一个掩码,并将 MS n bytes 设置为全一。当 n 不是 8 的倍数时,您只需要修复任何剩余的位。

我建议尝试这样的事情:

- init vector A = all (8 bit) elements to the residual mask of n % 8 bits
- init vector B = mask of n / 8 bytes using one of the above-mentioned methods
- init vector C = mask of (n + 7) / 8 bytes using one of the above-mentioned methods
- result = A | B & C

例如,如果 n = 36:

A = f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0
B = ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00
C = ff ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00
==> ff ff ff ff f0 00 00 00 00 00 00 00 00 00 00 00

根据需要,这将是无分支的,但它可能是大约 10 条指令。可能有更有效的方法,但我需要多考虑一下。

【讨论】:

    【解决方案3】:

    接下来的两个解决方案是Paul R's answer 的替代方案。 当在性能关键循环的上下文中需要掩码时,这些解决方案很有意义。


    SSE2

    __m128i bit_mask_v2(unsigned int n){                      /* Create an __m128i vector with the n most significant bits set to 1  */
        __m128i ones_hi   = _mm_set_epi64x(-1,0);             /* Binary vector of bits 1...1 and 0...0                               */
        __m128i ones_lo   = _mm_set_epi64x(0,-1);             /* Binary vector of bits 0...0 and 1...1                               */
        __m128i cnst64    = _mm_set1_epi64x(64);
        __m128i cnst128   = _mm_set1_epi64x(128);
    
        __m128i shift     = _mm_cvtsi32_si128(n);             /* Move n to SSE register                                              */
        __m128i shift_hi  = _mm_subs_epu16(cnst64,shift);     /* Subtract with saturation                                            */
        __m128i shift_lo  = _mm_subs_epu16(cnst128,shift);   
        __m128i hi        = _mm_sll_epi64(ones_hi,shift_hi);  /* Shift the hi bits 64-n positions if 64-n>=0, else no shift          */
        __m128i lo        = _mm_sll_epi64(ones_lo,shift_lo);  /* Shift the lo bits 128-n positions if 128-n>=0, else no shift        */
                   return   _mm_or_si128(lo,hi);              /* Merge hi and lo                                                     */
    }
    


    SSSE3 SSSE3 案例更有趣。 pshufb 指令用作小型查找表。我花了一些时间才弄清楚(饱和)算术和常量的正确组合。

    __m128i bit_mask_SSSE3(unsigned int n){                   /* Create an __m128i vector with the n most significant bits set to 1   */
        __m128i sat_const = _mm_set_epi8(247,239,231,223,   215,207,199,191,   183,175,167,159,   151,143,135,127);  /* Constant used in combination with saturating addition */
        __m128i sub_const = _mm_set1_epi8(248);
        __m128i pshub_lut = _mm_set_epi8(0,0,0,0,   0,0,0,0,   
                              0b11111111, 0b11111110, 0b11111100, 0b11111000,
                              0b11110000, 0b11100000, 0b11000000, 0b10000000);
    
        __m128i shift_bc  = _mm_set1_epi8(n);                         /* Broadcast n to the 16 8-bit elements.                                */
        __m128i shft_byte = _mm_adds_epu8(shift_bc,sat_const);        /* The constants sat_const and sub_const are selected such that         */
        __m128i shuf_indx = _mm_sub_epi8(shft_byte,sub_const);        /* _mm_shuffle_epi8 can be used as a tiny lookup table                  */
                    return  _mm_shuffle_epi8(pshub_lut,shuf_indx);    /* which finds the right bit pattern at the right position.             */
    }
    


    功能
    对于 OP 指定的 1&lt;=n&lt;=128,函数 bit_mask_Paul_R(n)(Paul R 的回答), 和bit_mask_v2(n) 产生相同的结果:

    bit_mask_Paul_R(  0) = FFFFFFFFFFFFFFFF 0000000000000000
    bit_mask_Paul_R(  1) = 8000000000000000 0000000000000000
    bit_mask_Paul_R(  2) = C000000000000000 0000000000000000
    bit_mask_Paul_R(  3) = E000000000000000 0000000000000000
    .....
    bit_mask_Paul_R(126) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFC
    bit_mask_Paul_R(127) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFE
    bit_mask_Paul_R(128) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
    
    
    bit_mask_v2(  0) = 0000000000000000 0000000000000000
    bit_mask_v2(  1) = 8000000000000000 0000000000000000
    bit_mask_v2(  2) = C000000000000000 0000000000000000
    bit_mask_v2(  3) = E000000000000000 0000000000000000
    .....
    bit_mask_v2(126) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFC
    bit_mask_v2(127) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFE
    bit_mask_v2(128) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
    
    
    bit_mask_SSSE3(  0) = 0000000000000000 0000000000000000
    bit_mask_SSSE3(  1) = 8000000000000000 0000000000000000
    bit_mask_SSSE3(  2) = C000000000000000 0000000000000000
    bit_mask_SSSE3(  3) = E000000000000000 0000000000000000
    .....
    bit_mask_SSSE3(126) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFC
    bit_mask_SSSE3(127) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFE
    bit_mask_SSSE3(128) = FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF
    

    对于n=0,最合理的结果是零向量,即 由bit_mask_v2(n)bit_mask_SSSE3(n) 制作。


    性能
    为了大致了解不同功能的性能,使用了以下代码:

    __m128i sum = _mm_setzero_si128();
    for (i=0;i<1000000000;i=i+1){
        sum=_mm_add_epi64(sum,bit_mask_Paul_R(i));   // or use next line instead 
    //    sum=_mm_add_epi64(sum,bit_mask_v2(i));
    //    sum=_mm_add_epi64(sum,bit_mask_SSSE3(i));
    }
    _mm_storeu_si128((__m128i*)x,sum);
    printf("sum = %016lX %016lX\n", x[1],x[0]);
    

    代码的性能稍微取决于指令编码的类型。 GCC 选项opts1 = -O3 -m64 -Wall -march=nehalem 导致非 vex 编码的 sse 指令, 而opts2 = -O3 -m64 -Wall -march=sandybridge 编译为 vex 编码的 avx128 指令。

    gcc 5.4 的结果是:

    Cycles per iteration on Intel Skylake, estimated with: perf stat -d ./a.out
                         opts1       opts2
    bit_mask_Paul_R       6.0         7.0
    bit_mask_v2           3.8         3.3
    bit_mask_SSSE3        3.0         3.0
    

    在实践中,性能将取决于 cpu 类型和周围的代码。 bit_mask_SSSE3 的性能受端口 5 压力限制; 每次迭代的三个指令(一个 movd 和两个 pshufb-s)由端口 5 处理。

    使用 AVX2,可以编写更高效的代码,see here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多