首页 > 解决方案 > 自动矢量化随机播放指令

问题描述

我试图让编译器(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只使用普通代码并依赖编译器的自动矢量化器来生成指令?

标签: csseavx2auto-vectorization

解决方案


(更新:自 2019-02-07 以来的新答案。)

即使没有我在此问题的先前答案中(v)pshufd 使用的 gcc 向量扩展,也 可以使编译器生成指令。以下示例给出了可能性的印象。这些示例使用 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=nehalem的主循环shuff1变为:

.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引出非车道交叉vpshufd ymm0, mem指示,完善。


示例 4

shuff5如果我们替换b[i+5] = a[i+1]; 为,组装会变得非常混乱b[i+5] = 0;。然而,循环被矢量化了。有关此答案中讨论的所有示例,另请参见此 Godbolt 链接


如果数组ab是 16(或 32)字节对齐的,那么我们可以使用 a = (int32_t*)__builtin_assume_aligned(a, 16); b = (int32_t*)__builtin_assume_aligned(b, 16); (或 32 而不是 16)。这有时会稍微改进汇编代码的生成。


推荐阅读