【问题标题】:How to combine two __m128 values to __m256?如何将两个 __m128 值组合到 __m256?
【发布时间】:2012-06-22 10:12:10
【问题描述】:

我想将两个__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 };

我可以使用任何内在函数来执行此操作吗?

【问题讨论】:

    标签: c x86 sse simd avx


    【解决方案1】:

    这应该做你想做的:

    __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

    他们的完整家族在这里:

    【讨论】:

    • Visual Studio 的某些版本(肯定是 2010,也可能是更高版本)在处理 _mm256_castps128_ps256 时存在错误,因此此代码可能会在它们上崩溃。见connect.microsoft.com/VisualStudio/feedback/details/653771/…。如果您需要您的代码在这些编译器上工作,则需要使用 user1584773 提供的解决方案,将其替换为插入。
    • 请注意,这会导致__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
    • 如果你“插入”到下半部分”,通常最好使用_mm256_blend_ps 而不是_mm256_insertf128_ps。更低的延迟并在更多端口上运行。只有vinsertf128可能比 vblendps ymm, ymm, imm8 更好的是使用内存源,用仅 16 字节加载而不是 32 字节加载替换向量的低通道。
    【解决方案2】:

    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 实际实现 _mm256_set_m128 的方式。

    编译器版本支持_mm256_set_m128/_mm256_setr_m128

    • clang:3.6 和更新版本。 (主线,关于 Apple 的 IDK)
    • 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 来使用vectorcall 约定,因此args 将在寄存器中,以便为MSVC 提供有效的非内联函数定义。)如果MSVC 可以将结果写入第三个函数,那么它可能可以内联到更大的函数中寄存器,而不是强制它读取 xmm0 并写入 ymm0 的调用约定。


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

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

    【讨论】:

      【解决方案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 未初始化,但您可以忽略它,如果您正在寻找性能,此解决方案将使用比另一个解决方案更少的时钟周期。

      【讨论】:

      • 你确定这比我的 Mystical 提出的解决方案更快吗?据我所知 castps128_ps256 是免费的,不是吗?此外,我的应用程序极大地受益于使用强制转换而不是插入(提取也是如此)。
      • @user1829358:低插入有望优化掉,但无需让您的编译器工作以删除不需要的东西。 (通过阅读尚未初始化的c,它也有未定义的行为,所以我强烈建议不要这样做。)是的,演员表显然更好; cast 在 asm 中是免费的,你只需要 1 个 vinsertf128 指令。
      【解决方案4】:

      也可以使用置换内在函数:

      __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);
      

      我不知道哪种方式更快。

      【讨论】:

      • 如果它真的编译成vperm2f128,它在Zen1上会更慢,并且在Intel上与vinsertf128相比没有优势。
      【解决方案5】:

      我相信这是最简单的:

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

      __m256 c = _mm256_set_m128(a, b);

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

      【讨论】:

      • Intel documents _mm256_set_m128(__m128 hi, __m128 lo) - 你应该只使用它,而不是自己定义它。
      • 正确,它应该已经定义了,但如果你使用的是旧版本的 msvc,它可能没有定义
      猜你喜欢
      • 2012-10-22
      • 1970-01-01
      • 2018-02-02
      • 2011-08-21
      • 1970-01-01
      • 2021-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多