自动向量化随机播放指令

时间:2019-02-02 01:08:40

标签: c sse avx2 auto-vectorization

我试图使编译器生成的(v)pshufd通过自动矢量指令(或等同物)。这非常困难。

例如,假设一个向量包含4个uint32值,则转换: A|B|C|D => A|A|C|C是应该使用单个指令来实现。(对应的固有:_mm_shuffle_epi32()

尝试仅使用常规操作来表达相同的转换,我可以写例如:

    for (i=0; i<4; i+=2)
        v32x4[i] = v32x4[i+1];

在编译器似乎无法进行良好的变换,而不是产生在标量和向量的代码的混合十几指令。 手动展开会产生更糟糕的结果。

有时,会有一些细节妨碍您进行编译,以免编译器正确翻译。例如,阵列中元件的NB应该是2的清晰功率,指针表应保证不别名,对准应当明确表示等 在这种情况下,我还没有发现任何类似的原因,并且我仍然停留具有手动内在以产生合理的装配。

有一种仅使用正常代码,依靠编译器的自动向量化,以产生(v)pshufd指令?

1 个答案:

答案 0 :(得分:2)

(更新:自2019年2月7日以来的新答案。)

可以使编译器生成(v)pshufd 指令,即使没有我在 previous answer to this question。 以下示例给人以可能性的印象。 这些示例是使用gcc 8.2和clang 7编译的。


示例1

#include<stdint.h>
/*                                       vectorizes     */
/*   gcc -m64 -O3  -march=nehalem        Yes            */
/*   gcc -m64 -O3  -march=skylake        Yes            */
/*   clang -m64 -O3  -march=nehalem      No             */
/*   clang -m64 -O3  -march=skylake      No             */
void shuff1(int32_t* restrict a, int32_t* restrict b, int32_t n){
    /* this line is optional */  a = (int32_t*)__builtin_assume_aligned(a, 16); b = (int32_t*)__builtin_assume_aligned(b, 16);
    for (int32_t i = 0; i < n; i=i+4) {
        b[i+0] = a[i+0];
        b[i+1] = a[i+0];
        b[i+2] = a[i+2];
        b[i+3] = a[i+2];
    }
}


/*                                       vectorizes     */
/*   gcc -m64 -O3  -march=nehalem        Yes            */
/*   gcc -m64 -O3  -march=skylake        Yes            */
/*   clang -m64 -O3  -march=nehalem      Yes            */
/*   clang -m64 -O3  -march=skylake      Yes            */
void shuff2(int32_t* restrict a, int32_t* restrict b, int32_t n){
    /* this line is optional */  a = (int32_t*)__builtin_assume_aligned(a, 16); b = (int32_t*)__builtin_assume_aligned(b, 16);
    for (int32_t i = 0; i < n; i=i+4) {
        b[i+0] = a[i+1];
        b[i+1] = a[i+2];
        b[i+2] = a[i+3];
        b[i+3] = a[i+0];
    }
}

令人惊讶的是,clang只对数学意义上的排列进行矢量化处理, 不一般的洗牌。使用gcc -m64 -O3 -march=nehalemshuff1的主循环变为:

.L3:
  add edx, 1
  pshufd xmm0, XMMWORD PTR [rdi+rax], 160
  movaps XMMWORD PTR [rsi+rax], xmm0
  add rax, 16
  cmp edx, ecx
  jb .L3


示例2

/*                                       vectorizes     */
/*   gcc -m64 -O3  -march=nehalem        No             */
/*   gcc -m64 -O3  -march=skylake        No             */
/*   clang -m64 -O3  -march=nehalem      No             */
/*   clang -m64 -O3  -march=skylake      No             */
void shuff3(int32_t* restrict a, int32_t* restrict b){
    /* this line is optional */ a = (int32_t*)__builtin_assume_aligned(a, 16); b = (int32_t*)__builtin_assume_aligned(b, 16);
    b[0] = a[0];
    b[1] = a[0];
    b[2] = a[2];
    b[3] = a[2];
}


/*                                       vectorizes     */
/*   gcc -m64 -O3  -march=nehalem        Yes            */
/*   gcc -m64 -O3  -march=skylake        Yes            */
/*   clang -m64 -O3  -march=nehalem      Yes            */
/*   clang -m64 -O3  -march=skylake      Yes            */
void shuff4(int32_t* restrict a, int32_t* restrict b){
    /* this line is optional */ a = (int32_t*)__builtin_assume_aligned(a, 16); b = (int32_t*)__builtin_assume_aligned(b, 16);
    b[0] = a[1];
    b[1] = a[2];
    b[2] = a[3];
    b[3] = a[0];
}

带有gcc -m64 -O3 -march=skylake的程序集:

shuff3:
  mov eax, DWORD PTR [rdi]
  mov DWORD PTR [rsi], eax
  mov DWORD PTR [rsi+4], eax
  mov eax, DWORD PTR [rdi+8]
  mov DWORD PTR [rsi+8], eax
  mov DWORD PTR [rsi+12], eax
  ret
shuff4:
  vpshufd xmm0, XMMWORD PTR [rdi], 57
  vmovaps XMMWORD PTR [rsi], xmm0
  ret

再次(0,3,2,1)排列的结果与(2,2,0,0)随机排列的情况本质上不同。


示例3

/*                                       vectorizes     */
/*   gcc -m64 -O3  -march=nehalem        Yes            */
/*   gcc -m64 -O3  -march=skylake        Yes            */
/*   clang -m64 -O3  -march=nehalem      No             */
/*   clang -m64 -O3  -march=skylake      No             */
void shuff5(int32_t* restrict a, int32_t* restrict b, int32_t n){
    /* this line is optional */ a = (int32_t*)__builtin_assume_aligned(a, 32); b = (int32_t*)__builtin_assume_aligned(b, 32);
    for (int32_t i = 0; i < n; i=i+8) {
        b[i+0] = a[i+2];
        b[i+1] = a[i+7];
        b[i+2] = a[i+7];
        b[i+3] = a[i+7];
        b[i+4] = a[i+0];
        b[i+5] = a[i+1];
        b[i+6] = a[i+5];
        b[i+7] = a[i+4];
    }
}


/*                                       vectorizes     */
/*   gcc -m64 -O3  -march=nehalem        Yes            */
/*   gcc -m64 -O3  -march=skylake        Yes            */
/*   clang -m64 -O3  -march=nehalem      No             */
/*   clang -m64 -O3  -march=skylake      No             */
void shuff6(int32_t* restrict a, int32_t* restrict b, int32_t n){
    /* this line is optional */ a = (int32_t*)__builtin_assume_aligned(a, 32); b = (int32_t*)__builtin_assume_aligned(b, 32);
    for (int32_t i = 0; i < n; i=i+8) {
        b[i+0] = a[i+0];
        b[i+1] = a[i+0];
        b[i+2] = a[i+2];
        b[i+3] = a[i+2];
        b[i+4] = a[i+4];
        b[i+5] = a[i+4];
        b[i+6] = a[i+6];
        b[i+7] = a[i+6];
    }
}

gcc -m64 -O3 -march=skylake的主循环包含 我认为,行车过境shuff5随机播放指令令人印象深刻。 函数vpermd导致非车道交叉shuff6指令,完美。


示例4

如果我们替换vpshufd ymm0, memshuff5的汇编将变得很混乱 由b[i+5] = a[i+1];。然而,循环是矢量化的。另请参见此Godbolt link 对于此答案中讨论的所有示例。


如果数组b[i+5] = 0;a是16(或32)字节对齐的,那么我们可以使用 b a = (int32_t*)__builtin_assume_aligned(a, 16); (或32而不是16)。有时这可以改善汇编代码的生成。