使用变量使用_mm256_extract_epi32()内在函数索引simd向量

Bra*_*ram 6 simd intrinsics avx avx2

我正在使用AVX内在_mm256_extract_epi32().

我不完全确定我是否正确使用它,因为gcc不喜欢我的代码,而clang编译并运行它没有问题.

我是基于整数变量的值来提取通道,而不是使用常量.

使用clang3.8(或clang4)为avx2编译以下代码段时,它会生成代码并使用vpermd指令.

#include <stdlib.h>
#include <immintrin.h>
#include <stdint.h>

uint32_t foo( int a, __m256i vec )
{
    uint32_t e = _mm256_extract_epi32( vec, a );
    return e*e;
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我改用gcc,那么就说gcc 7.2然后编译器无法生成代码,错误如下:

In file included from /opt/compiler-explorer/gcc-7.2.0/lib/gcc/x86_64-linux-gnu/7.2.0/include/immintrin.h:41:0,
                 from <source>:2:
/opt/compiler-explorer/gcc-7.2.0/lib/gcc/x86_64-linux-gnu/7.2.0/include/avxintrin.h: In function 'foo':
/opt/compiler-explorer/gcc-7.2.0/lib/gcc/x86_64-linux-gnu/7.2.0/include/avxintrin.h:524:20: error: the last argument must be a 1-bit immediate
   return (__m128i) __builtin_ia32_vextractf128_si256 ((__v8si)__X, __N);
                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-7.2.0/lib/gcc/x86_64-linux-gnu/7.2.0/include/immintrin.h:37:0,
                 from <source>:2:
/opt/compiler-explorer/gcc-7.2.0/lib/gcc/x86_64-linux-gnu/7.2.0/include/smmintrin.h:449:11: error: selector must be an integer constant in the range 0..3
    return __builtin_ia32_vec_ext_v4si ((__v4si)__X, __N);
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

我有两个问题:

  1. 为什么使用变量时clang很好,gcc是否需要常量?
  2. 为什么gcc不能下定决心?首先它需要一个1位立即值,后来它想要一个0..3范围内整数常量,这些是不同的东西.

Intels Intrinsics Guide没有为_mm256_extract_epi32()的索引值指定约束,那么谁在这里,gcc还是clang?

wim*_*wim 6

显然GCC和Clang做出了不同的选择.恕我直言GCC通过不对变量索引实现这一点做出了正确的选择.内在_mm256_extract_epi32不会转化为单一指令.使用变量索引会导致代码效率非常低:Clang 3.8需要4条指令来实现它.GCC迫使程序员考虑更有效的代码,避免_mm256_extract_epi32使用变量索引.显然_mm256_extract_epi32,即使对于变量索引,使用其他内在函数进行仿真也是微不足道的:

uint32_t mm256_extract_epi32_var_indx(__m256i vec, int i )
{   
    __m128i indx = _mm_cvtsi32_si128(i);
    __m256i val  = _mm256_permutevar8x32_epi32(vec, _mm256_castsi128_si256(indx));
    return         _mm_cvtsi128_si32(_mm256_castsi256_si128(val));
}    
Run Code Online (Sandbox Code Playgroud)

这应该在内联后编译为三个指令:两个_mm256_extract_epi32s和一个vmovd.

请注意,Clang 3.8实现的vpermd不同于intrinsics指南所描述的,因为Clang掩盖了索引-m64 -march=skylake -O3.但是,mm256_extract_epi32_var_indx根据内在指南,指数应导致零结果.Clang(5.0)的往返记忆也不是很有效.

正如@Peter Cordes评论的那样:使用固定索引0,1,2或3,只_mm256_extract_epi32需要一条指令就可以从xmm寄存器中提取整数.对于固定索引4,5,6或7,需要两条指令.不幸的是,pextrd不存在处理256位ymm寄存器的指令.


下一个例子说明了我的答案:

一个以SIMD内在函数开头的天真程序员可能会编写以下代码来将元素0,1,...,j-1与vpextrdfrom 相加j<8.

mm256_extract_epi32_var_indx:
  vmovd xmm1, edi
  vpermd ymm0, ymm1, ymm0
  vmovd eax, xmm0
  vzeroupper
  ret
Run Code Online (Sandbox Code Playgroud)

使用Clang 3.8,这将编译为大约50条带分支和循环的指令.GCC无法编译此代码.显然,对这些元素求和的有效代码可能基于:1.屏​​蔽元素j,j + 1,...,7和2.计算水平和.


Pet*_*des 5

__N它说必须是一个1位立即是不是第二对Arg的_mm256_extract_epi32,这是作为对Arg的的一些功能__builtin_ia32_vextractf128_si256(大概是第3位)。然后它需要一个在 0..3 范围内的整数常量 for vpextrd,给你总共 3 位的索引。

_mm256_extract_epi32是一个复合内在函数,不是直接根据单个builtin函数定义的。

vpextrd r32, ymm, imm8不存在,只有 xmm 版本存在,所以_mm256_extract_epi32是围绕vextracti/f128/的包装器vpextrd。Gcc 选择仅使其适用于编译时常量,因此它始终最多编译为 2 条指令。

如果你想要运行时变量向量索引,你需要使用不同的语法;例如,存储到一个数组并加载一个标量,并希望 gcc 将其优化为 shuffle/extract。

或者定义一个具有正确元素宽度的 GNU C 本机向量类型,并foo[i]像数组一样使用它来索引它。

typedef int v8si __attribute__ ((vector_size (32)));
v8si tmp = foo;   // may need a cast to convert from __m256i
int element_i = tmp[i];
Run Code Online (Sandbox Code Playgroud)

__m256i在 gcc/clang 中被定义为long long元素向量,所以如果你直接用 索引它[],你会得到 qword 元素。(并且您的代码不会使用 MSVC 进行编译,而 MSVC 根本没有定义__m256i这种方式。)


我最近没有检查过 asm 中的任何一个:如果你关心效率,你可能想要使用你的运行时变量索引手动设计一个 shuffle,就像@Wim 的回答表明 clang 所做的那样。