设置SSE寄存器中的最后或前n位

时间:2014-04-02 08:58:48

标签: c++ x86 sse simd intrinsics

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

我该怎么做?

3 个答案:

答案 0 :(得分:3)

我将此添加为第二个答案,并留下第一个回答历史兴趣。您似乎可以使用_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

答案 1 :(得分:1)

您可以使用this question中的一种方法生成掩码,其中MS n 字节设置为全部。当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条指令的顺序。可能有一种更有效的方法,但我需要更多考虑一下。

答案 2 :(得分:1)

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


SSE2

node-app-1.0.0.exe


SSSE3 SSSE3案例更有趣。 __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 */ } 指令用作小型查找表。花了一些时间才弄清楚(饱和)算法和常数的正确组合。

pshufb


功能
对于OP指定的__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. */ } ,函数1<=n<=128(Paul R&#39;答案), 和bit_mask_Paul_R(n)产生相同的结果:

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=0bit_mask_v2(n)生成。


<强>性能
为了粗略地了解不同函数的性能,使用了以下代码:

bit_mask_SSSE3(n)

代码的性能稍微取决于指令编码的类型。 GCC选项__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]); 导致非vex编码的sse指令, 而opts1 = -O3 -m64 -Wall -march=nehalem编译为vex编码的avx128指令。

gcc 5.4的结果是:

opts2 = -O3 -m64 -Wall -march=sandybridge

实际上,性能取决于cpu类型和周围代码。 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 的表现受到港口5压力的限制; 每次迭代的三条指令(一个bit_mask_SSSE3和两个movd - s)由端口5处理。

使用AVX2,可以实现更高效的代码see here