英特尔文档提供了__m256 _mm256_set_m128(__m128 hi, __m128 lo)
和_mm256_setr_m128(lo, hi)
两个内置函数,用于vinsertf128
指令,这正是您需要的1。(当然,还有__m256d
和__m256i
版本,它们使用相同的指令。如果支持AVX2,则__m256i版本可能使用vinserti128
,否则也将使用f128。)
这些内在函数现在都被四大x86编译器(gcc、clang、MSVC和ICC)的最新版本所支持。但是旧版本不支持,就像英特尔文档中其他帮助器内在函数一样,广泛实施起来很慢。(通常情况下,GCC或Clang是最后一个没有你想用的东西的持有者)。
如果不需要对旧版本的GCC进行移植,可以使用它:这是表达您想要的内容最易读的方式,遵循众所周知的_mm_set和_mm_setr模式。
从性能上讲,它当然与手动转换+ vinsertf128内在函数(@Mysticial's answer)一样有效,至少对于GCC来说,这实际上就是内部.h如何实现_mm256_set_m128。
_mm256_set_m128
/
_mm256_setr_m128
的编译器版本支持:
- clang: 3.6及以上版本(主线,不确定苹果是否包含)
- GCC:8.x及以上版本,最近的GCC7中不存在!
- ICC:至少从ICC13开始,在Godbolt上是最早的。
- MSVC:至少从19.14和19.10(WINE)VS2015开始,在Godbolt上是最早的。
https://godbolt.org/z/1na1qr 包含所有4个编译器的测试用例。
__m256 combine_testcase(__m128 hi, __m128 lo) {
return _mm256_set_m128(hi, lo);
}
他们都将这个函数编译为一个
vinsertf128
,只有MSVC例外,即使是最新版本也会浪费一个
vmovups xmm2, xmm1
复制寄存器。(我使用了
-O2 -Gv -arch:AVX
来使用矢量调用约定,以便参数在寄存器中,从而可以为MSVC创建一个高效的非内联函数定义。)假设MSVC可以将结果写入第三个寄存器,那么将其内联到更大的函数中应该是可以的,而不是调用约定强制它读取xmm0并写入ymm0。
注脚1:
vinsertf128
在 Zen1 上非常高效,在其他具有 256 位宽派生单元的 CPU 上与
vperm2f128
一样高效。它还可以从内存中获取高半部分,以防编译器将其溢出或将
_mm_loadu_ps
折叠到其中,而无需单独进行 128 位加载到寄存器中;
vperm2f128
的内存操作数将是一个 256 位加载,这是不希望发生的。
https://uops.info/ / https://agner.org/optimize/
__m256{ 4, 3, 2, 1, 8, 7, 6, 5 }
而不是__m256{ 1, 2, 3, 4, 5, 6, 7, 8 }
。我认为OP想使用_mm_setr_ps
而不是_mm_set_ps
。 - plasmacel_mm256_blend_ps
而不是_mm256_insertf128_ps
。延迟更低且在更多端口上运行。唯一的情况是vinsertf128
可能比vblendps ymm, ymm, imm8
更好的是使用内存源,用仅 16 字节的加载替换向量的低通道,而不是 32 字节的加载。 - Peter Cordes