C 自动矢量化洗牌指令

C 自动矢量化洗牌指令,c,sse,avx2,auto-vectorization,C,Sse,Avx2,Auto Vectorization,我试图让编译器通过自动矢量化生成(v)pshufd指令(或等效指令)。这非常困难 例如,假定向量为4uint32值,则转换: A | B | C | D=>A | A | C | C应该使用一条指令来实现(相应的内在指令:_mm_shuffle_epi32()) 尝试仅使用普通操作来表示相同的转换,我可以编写以下示例: for (i=0; i<4; i+=2) v32x4[i] = v32x4[i+1]; (i=0;i(更新:2019-02-07年以来的新答案。)

我试图让编译器通过自动矢量化生成
(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];
(i=0;i(更新:2019-02-07年以来的新答案。)

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


示例1

#包括
/*矢量化*/
/*gcc-m64-O3-march=nehalem是*/
/*gcc-m64-O3-march=天湖是*/
/*叮当声-m64-O3-march=nehalem号*/
/*叮当声-m64-O3-march=天湖号*/
void shuff1(int32_t*限制a、int32_t*限制b、int32_t n){
/*这一行是可选的*/a=(int32_t*)内置假设对齐(a,16);b=(int32_t*)内置假设对齐(b,16);
对于(int32_t i=0;i
令人惊讶的是,叮当声只在数学意义上对排列进行矢量化, 不是一般的洗牌。使用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=nehalem是*/
/*gcc-m64-O3-march=天湖是*/
/*叮当声-m64-O3-march=nehalem号*/
/*叮当声-m64-O3-march=天湖号*/
void shuff5(int32_t*限制a、int32_t*限制b、int32_t n){
/*这一行是可选的*/a=(int32_t*)内置假设对齐(a,32);b=(int32_t*)内置假设对齐(b,32);
对于(int32_t i=0;i
使用
gcc-m64-O3-march=skylake
shuff5的主循环包含 车道交叉
vpermd
shuffle指令,我认为这非常令人印象深刻。 功能
shuff6
导致非车道交叉
vpshufd ymm0,mem
指令,完美


示例4

如果我们替换
b[i+5]=a[i+1];
通过
b[i+5]=0;
。尽管如此,循环还是矢量化的。另请参见此 对于本答案中讨论的所有示例


如果数组
a
b
是16(或32)字节对齐的,那么我们可以使用
a=(int32\u t*)内置假设对齐(a,16);
b=(int32\u t*)内置假设对齐(b,16);

(或32而不是16)。这有时会稍微改进汇编代码的生成。

问题在哪里?如何生成
(v)pshufd
对于普通代码,您的示例中是否打算使用两个不同的变量名?对我来说,您似乎在问为什么您的C代码没有矢量化到特定的指令。给出该示例后,答案是您的C代码与指令做的事情不同。也许我遗漏了一些东西。Shugi知道这不是一个很好的例子回答这个问题,但是使用
\u builtin\u shuffle
和/或
\u builtin\u shuffleevector
至少可以加速GCC/clang吗?请小心,因为ICC不支持
\u builtin\u shuffle
,即使它声称是GCC的一个版本(或者至少上次我没有检查过)如果你想要一个宏.Ugh,使用
-march=skylake
,gcc会疯狂地使用
vmovd
vextractps
到EAX,
vinsertps
vpinsrd
。clang仍然使用
vmovsldup
a