Xeon Phi 的 reduce 操作的内联组装

inline assembly of reduce operation for Xeon Phi

我正在寻找 Xeon Phi 的加减操作的内联汇编操作。我在 intel intrinsic 网站 (link) 上找到了 _mm512_reduce_add_epi32 intrinsic。但是在网站上,他们并没有提到实际的组装操作。

谁能帮我找到Xeon Phi 平台上归约操作的内联汇编?

谢谢

在阅读汇编方面我几乎一无所知,所以我就这样做了:

创建了一个 foo.c 文件,如下所示:

#include "immintrin.h"

int foo(__m512i a) {
    return _mm512_reduce_add_epi32(a);
}

我使用 -mmic -S 符合英特尔编译器版本 16.0.1。它给了我以下汇编代码:

# -- Begin  foo
    .text
# mark_begin;
# Threads 4
        .align    16,0x90
    .globl foo
# --- foo(__m512i)
foo:
# parameter 1: %zmm0
..B1.1:                         # Preds ..B1.0 Latency 53
    .cfi_startproc
..___tag_value_foo.1:
..L2:
                                                          #3.20
        movl      , %eax                                      #4.12 c1
        vpermf32x4 8, %zmm0, %zmm1                           #4.12 c5
        kmov      %eax, %k1                                     #4.12 c5
        vpaddd    %zmm0, %zmm1, %zmm3                           #4.12 c9
        nop                                                     #4.12 c13
        vpermf32x4 , %zmm3, %zmm2                            #4.12 c17
        vpaddd    %zmm3, %zmm2, %zmm4                           #4.12 c21
        nop                                                     #4.12 c25
        vpaddd    %zmm4{badc}, %zmm4, %zmm5                     #4.12 c29
        nop                                                     #4.12 c33
        vpaddd    %zmm5{cdab}, %zmm5, %zmm6                     #4.12 c37
        nop                                                     #4.12 c41
        vpackstorelps %zmm6, -8(%rsp){%k1}                      #4.12 c45
        movl      -8(%rsp), %eax                                #4.12 c49
        ret                                                     #4.12 c53
        .align    16,0x90
    .cfi_endproc
                                # LOE
# mark_end;
    .type   foo,@function
    .size   foo,.-foo
    .data
# -- End  foo
    .data
    .section .note.GNU-stack, ""
// -- Begin DWARF2 SEGMENT .eh_frame
    .section .eh_frame,"a",@progbits
.eh_frame_seg:
    .align 8
# End

我想你应该能找到自己的出路...

使用 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 位向量来完成。英特尔已经创建了 KNC 独有的指令来执行此操作。

查看 Giles answer 中的程序集,我们可以看到它的作用。首先,它使用 KNC 独有的指令将高 256 位置换为低 256 位,如下所示:

vpermf32x4 8, %zmm0, %zmm1

238 是以 4 为基数的 3232。因此 zmm1 就四个 128 位通道而言是 (3,2,3,2)

接下来它进行向量求和

vpaddd    %zmm0, %zmm1, %zmm3

这给出了四个 128 位通道 (3+3, 2+2, 3+1, 2+0)

然后它排列第二个 128 位通道给出 (3+1, 3+1, 3+1, 3+1) 像这样

vpermf32x4 , %zmm3, %zmm2

其中 85 是以 4 为基数的 1111。然后将它们相加

vpaddd    %zmm3, %zmm2, %zmm4 

因此 zmm4 中的低 128 位通道包含四个 128 位通道的总和 (3+2+1+0)

此时需要在每个 128 位通道中排列 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      8, %zmm0, %zmm0, %zmm1
vpaddd          %zmm1, %zmm0, %zmm1
vshufi64x2      , %zmm1, %zmm1, %zmm1
vpaddd          %zmm1, %zmm0, %zmm1
vpshufd         , %zmm1, %zmm1
vpaddd          %zmm0, %zmm1, %zmm1
vpshufd         1, %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 的优势。