如何创建__m128i
设置n
最高有效位(在整个向量中)?我需要这个来屏蔽与计算相关的缓冲区部分。如果可能,解决方案应该没有分支,但这似乎很难实现
我该怎么做?
答案 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=0
和bit_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。