我想将 SSE/AVX 寄存器左移或右移 32 位的倍数,同时移入零。
让我更准确地说明我感兴趣的移位。对于 SSE,我想对四个 32 位浮点数进行以下移位:
shift1_SSE: [1, 2, 3, 4] -> [0, 1, 2, 3]
shift2_SSE: [1, 2, 3, 4] -> [0, 0, 1, 2]
对于 AVX,我想进行以下轮班:
shift1_AVX: [1, 2, 3, 4, 5, 6, 7, 8] -> [0, 1, 2, 3, 4, 5, 6, 7]
shift2_AVX: [1, 2, 3, 4, 5, 6, 7, 8] -> [0, 0, 1, 2, 3, 4, 5, 6]
shift3_AVX: [1, 2, 3, 4 ,5 ,6, 7, 8] -> [0, 0, 0, 0, 1, 2, 3, 4]
对于SSE,我想出了以下代码
shift1_SSE = _mm_castsi128_ps(_mm_slli_si128(_mm_castps_si128(x), 4));
shift2_SSE = _mm_shuffle_ps(_mm_setzero_ps(), x, 0x40);
//shift2_SSE = _mm_castsi128_ps(_mm_slli_si128(_mm_castps_si128(x), 8));
SSE 有更好的方法吗?
对于 AVX,我提出了以下需要 AVX2 的代码(并且未经测试)。 编辑(正如 Paul R 所解释的,此代码不起作用)。
shift1_AVX2 =_mm256_castsi256_ps(_mm256_slli_si256(_mm256_castps_si256(x), 4)));
shift2_AVX2 =_mm256_castsi256_ps(_mm256_slli_si256(_mm256_castps_si256(x), 8)));
shift3_AVX2 =_mm256_castsi256_ps(_mm256_slli_si256(_mm256_castps_si256(x), 12)));
如何使用 AVX 而不是 AVX2 做到最好(例如使用
_mm256_permute
或 _mm256_shuffle`)? 使用 AVX2 有更好的方法吗?
编辑:
Paul R 告诉我,我的 AVX2 代码将无法工作,并且 AVX 代码可能不值得。 对于 AVX2,我应该使用
_mm256_permutevar8x32_ps
和 _mm256_and_ps
。我没有带有 AVX2 (Haswell) 的系统,所以很难测试。
编辑: 根据 Felix Wyss 的回答,我提出了一些 AVX 解决方案,其中 shift1_AVX 和 shift2_AVX 只需要 3 个内在函数,shift3_AVX 只需要一个内在函数。 这是因为
_mm256_permutef128Ps
具有 归零功能。
shift1_AVX
__m256 t0 = _mm256_permute_ps(x, _MM_SHUFFLE(2, 1, 0, 3));
__m256 t1 = _mm256_permute2f128_ps(t0, t0, 41);
__m256 y = _mm256_blend_ps(t0, t1, 0x11);
shift2_AVX
__m256 t0 = _mm256_permute_ps(x, _MM_SHUFFLE(1, 0, 3, 2));
__m256 t1 = _mm256_permute2f128_ps(t0, t0, 41);
__m256 y = _mm256_blend_ps(t0, t1, 0x33);
shift3_AVX
x = _mm256_permute2f128_ps(x, x, 41);
您可以使用
_mm256_permute_ps
、_mm256_permute2f128_ps
和 _mm256_blend_ps
进行右移,如下所示:
__m256 t0 = _mm256_permute_ps(x, 0x39); // [x4 x7 x6 x5 x0 x3 x2 x1]
__m256 t1 = _mm256_permute2f128_ps(t0, t0, 0x81); // [ 0 0 0 0 x4 x7 x6 x5]
__m256 y = _mm256_blend_ps(t0, t1, 0x88); // [ 0 x7 x6 x5 x4 x3 x2 x1]
结果在
y
。 为了向右旋转,请将排列掩码设置为 0x01
而不是 0x81
。 通过更改排列和混合控制字节,可以类似地完成向左移位/旋转和更大的移位/旋转。
您的 SSE 实现很好,但我建议您对两个班次都使用
_mm_slli_si128
实现 - 强制转换使其看起来很复杂,但实际上可以归结为每个班次只有一条指令。
不幸的是,您的 AVX2 实现无法工作。几乎所有 AVX 指令实际上只是在两个相邻 128 位通道上并行运行的两个 SSE 指令。因此,对于您的第一个 shift_AVX2 示例,您将得到:
0, 0, 1, 2, 0, 4, 5, 6
----------- ----------
LS lane MS lane
确实跨通道工作的少数指令之一是
_mm256_permutevar8x32_ps。请注意,您需要结合使用 _mm256_and_ps
来将移位的元素归零。另请注意,这是一个 AVX2 解决方案 - AVX 本身对于基本算术/逻辑运算以外的任何操作都非常有限,因此我认为如果没有 AVX2,您将很难有效地完成此操作。
_mm256_permutevar8x32_epi32
移动所需索引,
_mm256_set_epi32
创建掩码,
_mm256_and_si256
按位使用并将该掩码应用于前导零的向量。
示例:
x = _mm256_set_epi32(8,7,6,5,4,3,2,1); // x = [1,2,3,4,5,6,7,8]
tmp = _mm256_permutevar8x32_epi32(tmp1, _mm256_set_epi32(5,4,3,2,1,0,0,0));
// shifting indexes 2 position right
// tmp = [1,1,1,2,3,4,5,6]
__m256i mask = _mm256_set_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000);
// mask to make first 2 indexes zero
result = _mm256_and_si256(tmp2, mask); // result = [0,0,1,2,3,4,5,6]