【发布时间】:2017-03-10 02:50:46
【问题描述】:
问题
我想要做的是,如果我的向量为 27(不是 32!)int8_t:
x = {0,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}
我想首先将它循环向右移动 n(不是常数),例如如果 n=1:
x2 = {26,0,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}
然后这个向量用于执行一些非常复杂的计算,但是为了简单起见,我们假设下一步只是将它循环左移n,并将其存储到内存中。所以我应该有一个新的向量 27 int8_t:
y = {0,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}
所以有成千上万个这样的向量,性能在这里非常关键。我们使用的 CPU 支持 AVX2,因此我们希望使用它来加快速度。
我目前的解决方案
要获得x2,我使用两个_mm256_loadu_si256() 和一个_mm256_blendv_epi8():
int8_t x[31+27+31];
for(int i=0; i<27; i++){
x[31+i] = i;
}
__m256i mask = _mm256_set_epi32 (0x0, 0x00800000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0);
__m256i x_second_part = _mm256_loadu_si256((__m256i*)(x+31+1)); //{1,2,...,26}
__m256i x_first_part = _mm256_loadu_si256((__m256i*)(x+31-26)); //{0}
__m256i x2 = _mm256_blendv_epi8(x_second_part, x_first_part, mask); //{1,2,...,26, 0}
int8_t y[31+27+31];
_mm256_storeu_si256((__m256i*)(y+31-26), x2);
_mm256_storeu_si256((__m256i*)(y+31+1), x2);
x 和 y 被声明为大小为 [31+27+31] 的原因是在这种情况下 _mm256_loadu_si256() 和 _mm256_storeu_si256() 不会导致段错误。
我可以通过以下方式获取y 的值:
for(int i=0; i<27; i++){
cout << (int)y[31+i] << ' ';
}
新问题
不幸的是,所有向量在内存中必须是连续的,例如,如果总共有两个向量需要处理:
x = {[ 0, 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,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53]};
那么我不能只使用_mm256_storeu_si256() 将y 的值放回内存,因为当第二个向量的值写入内存时,它会覆盖第一个向量的一些值:
int8_t x[31+27+27+31];
int8_t y[31+27+27+31];
for(int i=0; i<27*2; i++){
x[31+i] = i;
}
for(int i=0; i<2; i++){
__m256i mask = _mm256_set_epi32 (0x0, 0x00800000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0);
__m256i x_second_part = _mm256_loadu_si256((__m256i*)(x+31+27*i+1)); //{1,2,...,26}
__m256i x_first_part = _mm256_loadu_si256((__m256i*)(x+31+27*i-26)); //{0}
__m256i x2 = _mm256_blendv_epi8(x_second_part, x_first_part, mask); //{1,2,...,26, 0}
_mm256_storeu_si256((__m256i*)(y+31+27*i-26), x2);
_mm256_storeu_si256((__m256i*)(y+31+27*i+1), x2);
}
for(int i=0; i<27; i++){
cout << (int)y[31+i] << ' ';
}cout << endl;
for(int i=0; i<27; i++){
cout << (int)y[31+27+i] << ' ';
}cout << endl;
会输出
0 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
而不是
0 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
所以我在考虑使用 maskstore。但是在Intel Intrinsic Guide 中我找不到_mm256_maskstore_epi8。这让我回到主题:
如何在C/C++中做_mm256_maskstore_epi8()?
【问题讨论】:
-
它不存在。唯一的字节粒度掩码存储是 128b-only MASKMOVDQU,它具有 NT 语义(弱排序、绕过缓存、驱逐您使用它的数据)。你通常不希望这样。 VPMASKMOVD/Q (epi32/64) 是唯一可用的 256b 掩码存储大小(以及没有 NT 语义的掩码存储,因此即使解包到两个 128b 通道也无济于事。即使是 SSE 指令的 VEX 编码,VMASKMOVDQU 也具有 NT 语义)。
-
非原子读取-修改-写入重叠的最后 5 个字节可以吗?如果是这样,加载它们并合并。 (还是前面的 5 个字节?)
-
我认为使用 128 位向量是更好的解决方案,因为您可以在不退出 27 字节数组的情况下执行保存/加载。
-
哦对了,做两个重叠的128b store!是的,那应该很好。使用 ALIGNR 设置重叠存储。
-
@PeterCordes。您的“两个重叠的 128b 商店”听起来像是要走的路。我会试一试。谢谢!
标签: c++ simd intrinsics avx avx2