我正在寻找内联汇编操作,以便为至强 Phi 添加减少操作。我在英特尔内部网站( link )上发现了 _mm512_reduce_add_epi32 内部。但是在网站上,他们并没有提到它的实际组装操作。
有人能帮我在至强融核平台上找到归约运算的内联汇编吗?
谢谢
最佳答案
使用 KNC 减少 16 个整数是一个有趣的案例,可以说明它与 AVX512 的不同之处。_mm512_reduce_add_epi32
内在函数仅受 Intel 编译(当前)支持。它是像 SVML 一样令人讨厌的许多指令内在函数之一。但我想我明白为什么英特尔在这种情况下实现了这种内在,因为 KNC 和 AVX512 的结果非常不同。
使用 AVX512 我会做这样的事情
__m256i hi8 = _mm512_extracti64x4_epi64(a,1);
__m256i lo8 = _mm512_castsi512_si256(a);
__m256i vsum1 = _mm256_add_epi32(hi8,lo8);
然后我会像在 AVX2 中一样减少
__m256i vsum2 = _mm256_hadd_epi32(vsum1,vsum1);
__m256i vsum3 = _mm256_hadd_epi32(vsum2,vsum2);
__m128i hi4 = _mm256_extracti128_si256(vsum3,1);
__m128i lo4 = _mm256_castsi256_si128(vsum3);
__m128i vsum4 = _mm_add_epi32(hi4, lo4);
int sum = _mm_cvtsi128_si32(vsum4);
看看英特尔如何使用 AVX512 实现
_mm512_reduce_add_epi32
会很有趣。但是 KNC 指令集不支持 AVX 或 SSE,所以一切都必须用 KNC 的完整 512 位 vector 来完成。为此,英特尔创建了 KNC 独有的指令。
从 Giles 的回答中查看程序集,我们可以看到它的作用。首先,它使用 KNC 独有的指令将高 256 位置换为低 256 位,如下所示:
vpermf32x4 $238, %zmm0, %zmm1
238
的值是以 4 为基数的 3232
。所以 zmm1
就四个 128 位 channel 而言是 (3,2,3,2)
。接下来它做一个 vector 和
vpaddd %zmm0, %zmm1, %zmm3
这给出了四个 128 位 channel
(3+3, 2+2, 3+1, 2+0)
然后它排列第二个 128 位 channel ,给出
(3+1, 3+1, 3+1, 3+1)
像这样vpermf32x4 $85, %zmm3, %zmm2
其中
85
是基数为 4 的 1111
。然后将这些加在一起vpaddd %zmm3, %zmm2, %zmm4
这样
zmm4
中较低的 128 位 channel 包含四个 128 位 channel (3+2+1+0)
的总和。此时,它需要对每个 128 位 channel 内的 32 位值进行置换。它再次使用 KNC 的独特功能,允许它同时置换和添加(或至少符号是唯一的)。
vpaddd %zmm4{badc}, %zmm4, %zmm5
产生
(a+b, a+b, c+d, c+d)
和
vpaddd %zmm5{cdab}, %zmm5, %zmm6
产生
(a+b+c+d , a+b+c+d , a+b+c+d, a+b+c+d)
。现在只需提取较低的 32 位即可。这是 AVX512 的替代解决方案,类似于 KNC 的解决方案
#include <x86intrin.h>
int foo(__m512i a) {
__m512i vsum1 = _mm512_add_epi32(a,_mm512_shuffle_i64x2(a,a, 0xee));
__m512i vsum2 = _mm512_add_epi32(a,_mm512_shuffle_i64x2(vsum1,vsum1, 0x55));
__m512i vsum3 = _mm512_add_epi32(a,_mm512_shuffle_epi32(vsum2, _MM_PERM_BADC));
__m512i vsum4 = _mm512_add_epi32(a,_mm512_shuffle_epi32(vsum3, _MM_PERM_CADB));
return _mm_cvtsi128_si32(_mm512_castsi512_si128(vsum4));
}
使用
gcc -O3 -mavx512f
这给出。vshufi64x2 $238, %zmm0, %zmm0, %zmm1
vpaddd %zmm1, %zmm0, %zmm1
vshufi64x2 $85, %zmm1, %zmm1, %zmm1
vpaddd %zmm1, %zmm0, %zmm1
vpshufd $78, %zmm1, %zmm1
vpaddd %zmm0, %zmm1, %zmm1
vpshufd $141, %zmm1, %zmm1
vpaddd %zmm0, %zmm1, %zmm0
vmovd %xmm0, %eax
ret
AVX512 使用
vshufi64x2
而不是 vpermf32x4
,KNC 将车道内的排列和添加与 {abcd} 符号(例如 vpaddd %zmm4{badc}, %zmm4, %zmm5
)结合起来。这基本上是使用 _mm256_hadd_epi32
实现的。我忘了我已经看过这个关于 AVX512 的问题了。 Here is another solution 。
这里值得一提的是 KNC 的内在函数(未经测试)。
int foo(__m512i a) {
__m512i vsum1 = _mm512_add_epi32(a,_mm512_permute4f128_epi32(a, 0xee));
__m512i vsum2 = _mm512_add_epi32(a,_mm512_permute4f128_epi32(vsum1, 0x55));
__m512i vsum3 = _mm512_add_epi32(a,_mm512_swizzle_epi32(vsum2, _MM_SWIZ_REG_BADC));
__m512i vsum4 = _mm512_add_epi32(a,_mm512_swizzle_epi32(vsum3, _MM_SWIZ_REG_CADB));
int32_t out[2];
_mm512_packstorelo_epi32(out, vsum4);
return out[0];
}
我没有看到 KNC 的
_mm512_permute4f128_epi32(a,imm8
) 和 AVX512 的 _mm512_shuffle_i32x4(a,a,imm8)
之间的功能差异。这种情况下的主要区别在于
_mm512_shuffle_epi32
生成 vpshufd
而 _mm512_swizzle_epi32
不会。这似乎是 KNC 优于 AVX512 的优势。
关于c - Xeon Phi 的 reduce 操作的内联组装,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34428061/