_mm256_insert_epi32()没有效果

问题描述 投票:0回答:1

我开始在 Linux 上使用 GCC 12 在 x86 上编码 AVX2。一切都按预期进行。除了以下片段:

#include <iostream>
#include <immintrin.h>
    
__m256i aVector = _mm256_setzero_si256();
_mm256_insert_epi32(aVector, 0x80000000, 0);
_mm256_insert_epi32(aVector, 0x83333333, 3);
_mm256_insert_epi32(aVector, 0x87777777, 7);
    
alignas(__m256i) uint32_t aArray[8];
_mm256_store_si256((__m256i*)aArray, aVector);
    
std::cout << aArray[0] << ", " << aArray[1] << ", " << aArray[2] << ", " 
          << aArray[3] << ", " << aArray[4] << ", " << aArray[5] << ", " 
          << aArray[6] << ", " << aArray[7] << std::endl;

我希望在输出中看到插入的数字。但我得到以下信息:

0, 0, 0, 0, 0, 0, 0, 0

我不知道出了什么问题。我没有收到任何错误或警告。 具有 64 位通道的代码变体具有相同的行为。

为什么插入没有效果?

c++ x86 insert simd avx2
1个回答
0
投票

修改后的向量就是返回值,

v = _mm256_insert_epi32(v, x, 3);

内在函数指南有原型,请参阅https://stackoverflow.com/tags/sse/info中的其他链接。
__m256i _mm256_insert_epi32 (__m256i a, __int32 i, const int index)

没有任何具有小写名称的英特尔内在函数通过引用修改其参数;小写名称是(或者可以是1)C 函数,并且 C 没有引用参数。如果它们有一个输出,那就是返回值。如果它们有多个输出,则会有一个返回值和一个指针 arg,例如

_addcarry_u64
,它返回进位并有一个
unsigned __int64 * out
arg。 (它通常无法有效编译,但按值进位返回才是问题所在,编译器经常使用
setc
将进位具体化到整数寄存器中。)

有一些全大写命名的内在函数,它们是 CPP 宏,遵循所有大写名称都是宏的通用约定,其他名称不是(除了可能作为实现细节)。最有用的一个是

_MM_SHUFFLE()
,它将四个整数填充到
pshufd
shufps
vpermq
等立即数的 2 位字段中。并且至少其中几个会修改其参数,例如
_MM_TRANSPOSE4_PS(__m128, __m128, __m128, __m128) 
指南


仅供参考,即使对于一个元素,插入常量也不是一种非常有效的方法。没有单一的说明;

vpinsrd
仅存在于零扩展至 256 位的 XMM 目标中。 (或者传统的 SSE
pinsrd
,它 会使上半部分保持不变,但会导致某些微架构上的 SSE/AVX 转换停止。编译器不会使用传统的 SSE 形式插入下半部分,即使它会快,例如
-mtune=skylake
-mtune=znver1
。)

插入三个常量的一种更快的方法是使用一个

_mm256_blend_epi32
(
vpblendd
) 和一个包含要插入元素的向量。希望 clang 将插入优化为混合... Godbolt: 关闭,它在一个混合中完成了低 128 位通道中的两个元素,但留下了高元素进行单独混合。就像它试图节省常量空间一样,但最终仍然使用了 32 字节常量,上半部分有 16 字节零。

__m256i manual_blend(__m256i aVector){
    __m256i vconst = _mm256_set_epi32(0x87777777, 0x86666666, 0x85555555, 0x84444444,
                                      0x83333333, 0x82222222, 0x81111111, 0x80000000);
    return _mm256_blend_epi32(aVector, vconst, 0b1000'1001);
}
# GCC  -O2  -Wall -march=x86-64-v3
manual_blend(long long __vector(4)):
        vpblendd        ymm0, ymm0, YMMWORD PTR .LC3[rip], 137
        ret

对比具有 3 个插入的类似函数,采用向量 arg 并返回修改后的版本(在 YMM0 中)。

# GCC -O2  -Wall -march=x86-64-v3
bar(long long __vector(4)):
        mov     eax, -2147483648
        vpinsrd xmm1, xmm0, eax, 0       # insert into the low half, keeping the orig unmodified in YMM0
        mov     eax, -2093796557
        vextracti128    xmm0, ymm0, 0x1  # get the high half of the original
        vpinsrd xmm1, xmm1, eax, 3         # second insert into low half
        mov     eax, -2022213769
        vpinsrd xmm0, xmm0, eax, 3       # insert into the high half
        vinserti128     ymm0, ymm1, xmm0, 0x1   # recombine halves
        ret

GCC 在这里做得很好,天真地使用

vpinsrd
,跨多个插入进行优化,只提取并放回高半部分一次,而不是在每个插入之间。

# -O2  -Wall -march=x86-64-v3
bar(long long __vector(4)):
        vblendps        ymm0, ymm0, ymmword ptr [rip + .LCPI1_0], 9 # ymm0 = mem[0],ymm0[1,2],mem[3],ymm0[4,5,6,7]
        vbroadcastss    ymm1, dword ptr [rip + .LCPI1_1] # ymm1 = [2272753527,2272753527,2272753527,2272753527,2272753527,2272753527,2272753527,2272753527]
        vblendps        ymm0, ymm0, ymm1, 128           # ymm0 = ymm0[0,1,2,3,4,5,6],ymm1[7]
        ret
不幸的是,Clang 即使在整数向量上也使用 FP 混合 (

blendps

);如果依赖链的一部分涉及实际的 SIMD 整数指令,如 
vpaddd
 (
_mm256_add_epi32
),则在 Skylake 等某些 Intel CPU 上,这将花费额外的延迟转发到混合和转发的周期。 (对于具有 SSE1 的非 AVX,
...ps
 打包单编码小于等效的 
...pd
 打包双精度或 
p...
 整数(
movaps
movdqa
),否则它们在机器中的大小相同代码。但通常它不会造成伤害,所以总是这样做就好。对于混合,它确实会造成伤害,但不会节省空间。还可能会损害某些微体系结构上的按位布尔运算的性能,IIRC。就像 Sandybridge 或 Haswell 的吞吐量一样
vandps
vpand
。)


脚注1:

在调试版本中,具有立即操作数的内在函数需要是 GCC 的 immintrin.h 中的宏,因为即使是

always_inline

 函数也无法获得常量传播以使参数到 GCC 
__builtin_ia32_...
 内置实际的编译时常量。但在优化构建中,GCC 标头使用函数定义;有一个 #ifdef 和第二组需要常量的内在函数的定义。

© www.soinside.com 2019 - 2024. All rights reserved.