我正在使用AVX内在_mm256_extract_epi32()。
我不完全确定我是否正确使用它,因为gcc不喜欢我的代码,而clang编译并运行它没有问题。
我是基于整数变量的值来提取通道,而不是使用常量。
使用clang3.8(或clang4)为avx2编译以下代码段时,它generates code并使用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);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
我有两个问题:
Intels Intrinsics Guide没有为_mm256_extract_epi32()的索引值指定约束,那么谁在这里,gcc还是clang?
答案 0 :(得分:6)
显然GCC和Clang做出了不同的选择。
恕我直言GCC通过不对变量索引实现这一点做出了正确的选择。内在_mm256_extract_epi32
不会转换为单个指令。使用变量索引,此内在函数可能会导致代码效率低下,
如果它用于性能关键循环。
例如,Clang 3.8需要4条指令来实现带有变量索引的_mm256_extract_epi32
。
GCC迫使程序员考虑更有效的代码,避免使用变量索引_mm256_extract_epi32
。
然而,有时候有一个模拟_mm256_extract_epi32
的可移植(gcc,clang,icc)函数是很有用的。
变量索引:
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));
}
这应该在内联后编译为三条指令:两个vmovd
和一个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
请注意,内在指南描述了索引的结果为0
&gt; = 8(无论如何这是一个不寻常的情况)。对于Clang 3.8和mm256_extract_epi32_var_indx
,索引以模8减少。换句话说:仅使用索引的3个最低有效位。
请注意,Clang 5.0的内存往返也不是很有效,
见this Godbolt link。 Clang 7.0
无法使用变量索引编译_mm256_extract_epi32
。
作为@Peter Cordes commented:使用固定索引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;
}
使用Clang 3.8,这将使用分支和循环编译为大约50 instructions。 GCC无法编译此代码。 显然,总结这些元素的有效代码可能基于: 掩盖元素j,j + 1,...,7和 2.计算水平和。
答案 1 :(得分:3)
__N
它必须是1位立即不第二个arg到_mm256_extract_epi32
,它的某些功能用作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将其优化为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];
gcc / clang中的 __m256i
被定义为long long
元素的向量,因此如果您直接使用[]
对其进行索引,则会获得qword元素。 (并且您的代码无法使用MSVC进行编译,而MSVC根本就没有定义__m256i
。)
我最近没有检查过asm:如果你关心效率,你可能想用你的运行时变量索引手动设计一个shuffle,就像@Wim的答案暗示铿锵确实