我正在使用AVX固有的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;
}现在,如果我使用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);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~我对此有两个问题:
顺便说一句,Intrinsics没有为_mm256_extract_epi32()的索引值指定约束,那么gcc还是clang,谁在这里呢?
发布于 2018-02-10 22:45:54
显然,GCC和Clang做出了不同的选择。国际水文学组织GCC做出了正确的选择,没有为可变指数实施这一政策。内部_mm256_extract_epi32不能转换为一条指令。如果在性能关键循环中使用变量索引,则此内在特性可能导致效率低下的代码。
例如,Clang3.8需要4个指令来实现带有变量索引的_mm256_extract_epi32。GCC强迫程序员考虑更有效的代码,以避免使用可变索引的_mm256_extract_epi32。
然而,有时有一个可移植的(gcc,clang,icc)函数是有用的,它通过变量索引来模拟_mm256_extract_epi32:
uint32_t mm256_extract_epi32_var_indx(const __m256i vec, const unsigned 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));
} 这应该编译成内联后的三个指令:两个vmovds和一个vpermd (gcc 8.2与-m64 -march=skylake -O3):
mm256_extract_epi32_var_indx:
vmovd xmm1, edi
vpermd ymm0, ymm1, ymm0
vmovd eax, xmm0
vzeroupper
ret注意,本质指南描述索引>=8的结果为0(无论如何这是一个不寻常的情况)。使用Clang3.8和mm256_extract_epi32_var_indx,索引的模块数减少了8。换句话说:只使用索引中最不重要的3位。请注意,Clang5.0的内存往返也不是很有效,请参阅这个该死的链接。Clang7.0无法用变量索引编译_mm256_extract_epi32。
作为@Peter Cordes评论:使用固定的索引0、1、2或3,只需要一个pextrd指令就可以从xmm寄存器中提取整数。对于固定的索引4、5、6或7,需要两个指令。不幸的是,不存在在256位ymm寄存器上工作的vpextrd指令.
下一个例子说明了我的答案:
从SIMD本质开始的天真程序员可能会编写以下代码,将来自j<8的元素0、1、.、j-1和来自vec的元素相加。
#include <stdlib.h>
#include <immintrin.h>
#include <stdint.h>
uint32_t foo( __m256i vec , int j)
{
uint32_t sum=0;
for (int i = 0; i < j; i++){
sum = sum + (uint32_t)_mm256_extract_epi32( vec, i );
}
return sum;
}使用Clang3.8,这将编译成大约50 使用说明的分支和循环。GCC没有编译这段代码。显然,将这些元素相加的有效代码可能基于:
发布于 2018-02-11 02:02:28
它说必须是1位即时的__N不是_mm256_extract_epi32的第二位arg,它是作为arg到__builtin_ia32_vextractf128_si256 (大概是第3位)的一些功能。然后,它需要一个vpextrd的0..3范围内的整数常量,总共给出3位索引。
_mm256_extract_epi32是一个复合的内在特性,不是直接用单个builtin函数来定义的。
vpextrd r32, ymm, imm8不存在,只有xmm版本存在,因此_mm256_extract_epi32是vextracti/f128 / vpextrd的包装器。Gcc选择只为编译时常量工作,所以它总是编译到最多2条指令。
如果需要运行时变量向量索引,则需要使用不同的语法;例如存储到数组并加载标量,并希望gcc将其优化为洗牌/提取。
或者定义具有正确元素宽度的GNU本机向量类型,并使用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];gcc/clang中的__m256i定义为long long元素的向量,所以如果直接用[]索引它,就会得到qword元素。(而且您的代码不会用MSVC编译,因为MSVC根本就没有那样定义__m256i。)
最近我还没有检查asm中的任何一个:如果您关心效率,您可能需要使用运行时变量索引手动设计洗牌,就像@Wim的回答表明clang是这样的。
https://stackoverflow.com/questions/48726032
复制相似问题