如何将两个__m128值组合到__m256?

时间:2012-06-20 09:40:18

标签: c x86 sse simd avx

我想将两个__m128值合并为一个__m256

这样的事情:

__m128 a = _mm_set_ps(1, 2, 3, 4);
__m128 b = _mm_set_ps(5, 6, 7, 8);

类似于:

__m256 c = { 1, 2, 3, 4, 5, 6, 7, 8 };

我可以使用任何内在函数来做到这一点吗?

5 个答案:

答案 0 :(得分:23)

这应该做你想要的:

__m128 a = _mm_set_ps(1,2,3,4);
__m128 b = _mm_set_ps(5,6,7,8);

__m256 c = _mm256_castps128_ps256(a);
c = _mm256_insertf128_ps(c,b,1);

如果订单与您想要的相反,则只需切换ab


感兴趣的内在因素是_mm256_insertf128_ps,它允许您将128位寄存器插入256位AVX寄存器的低半部分或高半部分:

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011/compiler_c/intref_cls/common/intref_avx_insertf128_ps.htm

他们的完整家庭在这里:

答案 1 :(得分:4)

Intel documents __m256 _mm256_set_m128(__m128 hi, __m128 lo)_mm256_setr_m128(lo, hi) 作为 vinsertf128 指令的内在函数,这正是您想要的1. (当然也有__m256d__m256i版本,它们使用相同的指令。如果AVX2可用,__m256i版本可能使用vinserti128,否则它也会使用f128。)

如今,所有 4 种主要 x86 编译器(gcc、clang、MSVC 和 ICC)的当前版本都支持这些内在函数。但不是旧版本;就像英特尔记录的其他一些辅助内在函数一样,广泛的实现一直很慢。 (通常 GCC 或 clang 是最后一个没有你希望可以便携使用的东西。)

如果您不需要可移植到旧的 GCC 版本,请使用它:这是表达您想要的内容的最易读的方式,遵循众所周知的 _mm_set_mm_setr 模式。

在性能方面,它当然与手动转换 + vinsertf128 内在函数(@Mysticial 的答案)一样有效,而且对于 gcc 而言,至少这实际上是内部 .h 实际实现 {{1} }.

编译器版本支持 _mm256_set_m128 / _mm256_set_m128

  • clang:3.6 及更新版本。 (主线,关于苹果的 IDK)
  • GCC:8.x 及更新版本,不像 GCC7 那样最近
  • ICC:至少从 ICC13 起,是 Godbolt 上最早的。
  • MSVC:至少从 19.14 和 19.10 (WINE) VS2015 开始,最早出现在 Godbolt 上。

https://godbolt.org/z/1na1qr 有所有 4 个编译器的测试用例。

_mm256_setr_m128

他们都将此函数编译为一个 __m256 combine_testcase(__m128 hi, __m128 lo) { return _mm256_set_m128(hi, lo); } ,除了 MSVC,即使是最新版本也浪费了 vinsertf128 复制寄存器。 (我使用 vmovups xmm2, xmm1 来使用 vectorcall 约定,因此 args 将在寄存器中,以便为 MSVC 提供有效的非内联函数定义。)如果 MSVC 可以将结果写入到更大的函数中,则大概可以将其内联到更大的函数中第三个寄存器,而不是强制它读取 xmm0 和写入 ymm0 的调用约定。


脚注 1:
-O2 -Gv -arch:AVX 在 Zen1 上非常高效,在其他具有 256 位宽 shuffle 单元的 CPU 上与 vinsertf128 一样高效。它还可以从内存中取出高半部分,以防编译器溢出或将 vperm2f128 折叠到其中,而不需要单独将 128 位加载到寄存器中; _mm_loadu_ps 的内存操作数将是您不想要的 256 位负载。

https://uops.info/ / https://agner.org/optimize/

答案 2 :(得分:3)

即使这个也可以使用:

__m128 a = _mm_set_ps(1,2,3,4);
__m128 b = _mm_set_ps(5,6,7,8);

__m256 c = _mm256_insertf128_ps(c,a,0);
c = _mm256_insertf128_ps(c,b,1);

你会收到一个警告,因为c没有被初始化但你可以忽略它,如果你正在寻找性能,这个解决方案将使用更少的时钟周期,而另一个。

答案 3 :(得分:2)

也可以使用permute intrinsic:

__m128 a = _mm_set_ps(1,2,3,4);
__m128 b = _mm_set_ps(5,6,7,8);
__m256 c = _mm256_permute2f128_ps(_mm256_castps128_ps256(a), _mm256_castps128_ps256(b), 0x20);

我不知道哪条路更快。

答案 4 :(得分:0)

我认为这是最简单的:

#define _mm256_set_m128(/* __m128 */ hi, /* __m128 */ lo) \ _mm256_insertf128_ps(_mm256_castps128_ps256(lo), (hi), 0x1)

__m256 c = _mm256_set_m128(a, b);

请注意 __mm256_set_m128 已在 msvc 2019 中定义,如果您#include "immintrin.h"