我正在寻找内联汇编操作,以便为至强 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/

10-11 15:15