gpt4 book ai didi

c++ - 在 AVX2 中重现 _mm256_sllv_epi16 和 _mm256_sllv_epi8

转载 作者:太空狗 更新时间:2023-10-29 20:20:23 27 4
gpt4 key购买 nike

我很惊讶地看到 _mm256_sllv_epi16/8(__m256i v1, __m256i v2)_mm256_srlv_epi16/8(__m256i v1, __m256i v2) 不在 Intel Intrinsics Guide 和我找不到任何解决方案来仅使用 AVX2 重新创建 AVX512 内在函数。

此函数将所有 16/8 位 packed int 左移 v2 中相应数据元素的计数值。

epi16 示例:

__m256i v1 = _mm256_set1_epi16(0b1111111111111111);
__m256i v2 = _mm256_setr_epi16(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
v1 = _mm256_sllv_epi16(v1, v2);

然后 v1 等于 -> (1111111111111111, 1111111111111110, 1111111111111100, 1111111111111000, ..............., 1000000000000000);

最佳答案

_mm256_sllv_epi8 情况下,使用 pshufb 指令作为一个小型查找表,用乘法代替移位并不难。也可以通过乘法和其他一些指令来模拟 _mm256_srlv_epi8 的右移,请参见下面的代码。我希望至少 _mm256_sllv_epi8 比 Nyan 的 solution 更有效率.


或多或少可以使用相同的想法来模拟 _mm256_sllv_epi16,但在这种情况下,选择正确的乘数就不那么简单了(另请参见下面的代码)。

下面的解决方案 _mm256_sllv_epi16_emu 不一定比 Nyan 的 solution 更快或更好.性能取决于周围的代码和所使用的 CPU。尽管如此,这里的解决方案可能是有用的,至少在较旧的计算机系统上是这样。例如,vpsllvd 指令在 Nyan 的解决方案中使用了两次。此指令在 Intel Skylake 系统或更新版本上速度很快。在 Intel Broadwell 或 Haswell 上,这条指令很慢,因为它解码为 3 个微操作。这里的解决方案避免了这种缓慢的指令。

如果已知移位计数小于或等于 15,则可以使用 mask_lt_15 跳过这两行代码。

缺失的内在 _mm256_srlv_epi16 作为练习留给读者。


/*     gcc -O3 -m64 -Wall -mavx2 -march=broadwell shift_v_epi8.c     */
#include <immintrin.h>
#include <stdio.h>
int print_epi8(__m256i a);
int print_epi16(__m256i a);

__m256i _mm256_sllv_epi8(__m256i a, __m256i count) {
__m256i mask_hi = _mm256_set1_epi32(0xFF00FF00);
__m256i multiplier_lut = _mm256_set_epi8(0,0,0,0, 0,0,0,0, 128,64,32,16, 8,4,2,1, 0,0,0,0, 0,0,0,0, 128,64,32,16, 8,4,2,1);

__m256i count_sat = _mm256_min_epu8(count, _mm256_set1_epi8(8)); /* AVX shift counts are not masked. So a_i << n_i = 0 for n_i >= 8. count_sat is always less than 9.*/
__m256i multiplier = _mm256_shuffle_epi8(multiplier_lut, count_sat); /* Select the right multiplication factor in the lookup table. */
__m256i x_lo = _mm256_mullo_epi16(a, multiplier); /* Unfortunately _mm256_mullo_epi8 doesn't exist. Split the 16 bit elements in a high and low part. */

__m256i multiplier_hi = _mm256_srli_epi16(multiplier, 8); /* The multiplier of the high bits. */
__m256i a_hi = _mm256_and_si256(a, mask_hi); /* Mask off the low bits. */
__m256i x_hi = _mm256_mullo_epi16(a_hi, multiplier_hi);
__m256i x = _mm256_blendv_epi8(x_lo, x_hi, mask_hi); /* Merge the high and low part. */
return x;
}


__m256i _mm256_srlv_epi8(__m256i a, __m256i count) {
__m256i mask_hi = _mm256_set1_epi32(0xFF00FF00);
__m256i multiplier_lut = _mm256_set_epi8(0,0,0,0, 0,0,0,0, 1,2,4,8, 16,32,64,128, 0,0,0,0, 0,0,0,0, 1,2,4,8, 16,32,64,128);

__m256i count_sat = _mm256_min_epu8(count, _mm256_set1_epi8(8)); /* AVX shift counts are not masked. So a_i >> n_i = 0 for n_i >= 8. count_sat is always less than 9.*/
__m256i multiplier = _mm256_shuffle_epi8(multiplier_lut, count_sat); /* Select the right multiplication factor in the lookup table. */
__m256i a_lo = _mm256_andnot_si256(mask_hi, a); /* Mask off the high bits. */
__m256i multiplier_lo = _mm256_andnot_si256(mask_hi, multiplier); /* The multiplier of the low bits. */
__m256i x_lo = _mm256_mullo_epi16(a_lo, multiplier_lo); /* Shift left a_lo by multiplying. */
x_lo = _mm256_srli_epi16(x_lo, 7); /* Shift right by 7 to get the low bits at the right position. */

__m256i multiplier_hi = _mm256_and_si256(mask_hi, multiplier); /* The multiplier of the high bits. */
__m256i x_hi = _mm256_mulhi_epu16(a, multiplier_hi); /* Variable shift left a_hi by multiplying. Use a instead of a_hi because the a_lo bits don't interfere */
x_hi = _mm256_slli_epi16(x_hi, 1); /* Shift left by 1 to get the high bits at the right position. */
__m256i x = _mm256_blendv_epi8(x_lo, x_hi, mask_hi); /* Merge the high and low part. */
return x;
}


__m256i _mm256_sllv_epi16_emu(__m256i a, __m256i count) {
__m256i multiplier_lut = _mm256_set_epi8(0,0,0,0, 0,0,0,0, 128,64,32,16, 8,4,2,1, 0,0,0,0, 0,0,0,0, 128,64,32,16, 8,4,2,1);
__m256i byte_shuf_mask = _mm256_set_epi8(14,14,12,12, 10,10,8,8, 6,6,4,4, 2,2,0,0, 14,14,12,12, 10,10,8,8, 6,6,4,4, 2,2,0,0);

__m256i mask_lt_15 = _mm256_cmpgt_epi16(_mm256_set1_epi16(16), count);
a = _mm256_and_si256(mask_lt_15, a); /* Set a to zero if count > 15. */
count = _mm256_shuffle_epi8(count, byte_shuf_mask); /* Duplicate bytes from the even postions to bytes at the even and odd positions. */
count = _mm256_sub_epi8(count,_mm256_set1_epi16(0x0800)); /* Subtract 8 at the even byte positions. Note that the vpshufb instruction selects a zero byte if the shuffle control mask is negative. */
__m256i multiplier = _mm256_shuffle_epi8(multiplier_lut, count); /* Select the right multiplication factor in the lookup table. Within the 16 bit elements, only the upper byte or the lower byte is nonzero. */
__m256i x = _mm256_mullo_epi16(a, multiplier);
return x;
}


int main(){

printf("Emulating _mm256_sllv_epi8:\n");
__m256i a = _mm256_set_epi8(32,31,30,29, 28,27,26,25, 24,23,22,21, 20,19,18,17, 16,15,14,13, 12,11,10,9, 8,7,6,5, 4,3,2,1);
__m256i count = _mm256_set_epi8(7,6,5,4, 3,2,1,0, 11,10,9,8, 7,6,5,4, 3,2,1,0, 11,10,9,8, 7,6,5,4, 3,2,1,0);
__m256i x = _mm256_sllv_epi8(a, count);
printf("a = \n"); print_epi8(a );
printf("count = \n"); print_epi8(count);
printf("x = \n"); print_epi8(x );
printf("\n\n");


printf("Emulating _mm256_srlv_epi8:\n");
a = _mm256_set_epi8(223,224,225,226, 227,228,229,230, 231,232,233,234, 235,236,237,238, 239,240,241,242, 243,244,245,246, 247,248,249,250, 251,252,253,254);
count = _mm256_set_epi8(7,6,5,4, 3,2,1,0, 11,10,9,8, 7,6,5,4, 3,2,1,0, 11,10,9,8, 7,6,5,4, 3,2,1,0);
x = _mm256_srlv_epi8(a, count);
printf("a = \n"); print_epi8(a );
printf("count = \n"); print_epi8(count);
printf("x = \n"); print_epi8(x );
printf("\n\n");



printf("Emulating _mm256_sllv_epi16:\n");
a = _mm256_set_epi16(1601,1501,1401,1301, 1200,1100,1000,900, 800,700,600,500, 400,300,200,100);
count = _mm256_set_epi16(17,16,15,13, 11,10,9,8, 7,6,5,4, 3,2,1,0);
x = _mm256_sllv_epi16_emu(a, count);
printf("a = \n"); print_epi16(a );
printf("count = \n"); print_epi16(count);
printf("x = \n"); print_epi16(x );
printf("\n\n");

return 0;
}


int print_epi8(__m256i a){
char v[32];
int i;
_mm256_storeu_si256((__m256i *)v,a);
for (i = 0; i<32; i++) printf("%4hhu",v[i]);
printf("\n");
return 0;
}

int print_epi16(__m256i a){
unsigned short int v[16];
int i;
_mm256_storeu_si256((__m256i *)v,a);
for (i = 0; i<16; i++) printf("%6hu",v[i]);
printf("\n");
return 0;
}

输出是:

Emulating _mm256_sllv_epi8:
a =
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
count =
0 1 2 3 4 5 6 7 8 9 10 11 0 1 2 3 4 5 6 7 8 9 10 11 0 1 2 3 4 5 6 7
x =
1 4 12 32 80 192 192 0 0 0 0 0 13 28 60 128 16 64 192 0 0 0 0 0 25 52 108 224 208 192 192 0


Emulating _mm256_srlv_epi8:
a =
254 253 252 251 250 249 248 247 246 245 244 243 242 241 240 239 238 237 236 235 234 233 232 231 230 229 228 227 226 225 224 223
count =
0 1 2 3 4 5 6 7 8 9 10 11 0 1 2 3 4 5 6 7 8 9 10 11 0 1 2 3 4 5 6 7
x =
254 126 63 31 15 7 3 1 0 0 0 0 242 120 60 29 14 7 3 1 0 0 0 0 230 114 57 28 14 7 3 1


Emulating _mm256_sllv_epi16:
a =
100 200 300 400 500 600 700 800 900 1000 1100 1200 1301 1401 1501 1601
count =
0 1 2 3 4 5 6 7 8 9 10 11 13 15 16 17
x =
100 400 1200 3200 8000 19200 44800 36864 33792 53248 12288 32768 40960 32768 0 0

确实缺少一些 AVX2 指令。但是,请注意,通过模拟“缺失”的 AVX2 指令来填补这些空白并不总是一个好主意。有时是以避免这些模拟指令的方式重新设计代码会更有效。例如,通过使用更宽的 vector 元素(_epi32 而不是 _epi16),具有 native 支持。

关于c++ - 在 AVX2 中重现 _mm256_sllv_epi16 和 _mm256_sllv_epi8,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51789685/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com