【问题标题】:SSE and AVX intrinsics mixtureSSE 和 AVX 内在函数混合
【发布时间】:2023-03-11 01:42:01
【问题描述】:

除了SSE-copy, AVX-copy and std::copy performance。假设我们需要以下列方式对某个循环进行矢量化:1)通过 AVX 对第一个循环批次(乘以 8)进行矢量化。 2) 将循环的剩余部分分成两批。通过 SSE 对 4 的倍数的批次进行矢量化。 3) 通过串行程序处理整个循环的剩余批次。让我们考虑复制数组的示例:

#include <immintrin.h>

template<int length,
         int unroll_bound_avx = length & (~7),
         int unroll_tail_avx  = length - unroll_bound_avx,
         int unroll_bound_sse = unroll_tail_avx & (~3),
         int unroll_tail_last = unroll_tail_avx - unroll_bound_sse>
void simd_copy(float *src, float *dest)
{
    auto src_  = src;
    auto dest_ = dest;

    //Vectorize first part of loop via AVX
    for(; src_!=src+unroll_bound_avx; src_+=8, dest_+=8)
    {
         __m256 buffer = _mm256_load_ps(src_);
         _mm256_store_ps(dest_, buffer);
    }

    //Vectorize remainder part of loop via SSE
    for(; src_!=src+unroll_bound_sse+unroll_bound_avx; src_+=4, dest_+=4)
    {
        __m128 buffer = _mm_load_ps(src_);
        _mm_store_ps(dest_, buffer);
    }

    //Process residual elements
    for(; src_!=src+length; ++src_, ++dest_)
        *dest_ = *src_;
}

int main()
{  
    const int sz = 15;
    float *src = (float *)_mm_malloc(sz*sizeof(float), 16);
    float *dest = (float *)_mm_malloc(sz*sizeof(float), 16);
    float a=0;
    std::generate(src, src+sz, [&](){return ++a;});

    simd_copy<sz>(src, dest);

    _mm_free(src);
    _mm_free(dest);
}

同时使用 SSE 和 AVX 是否正确?我需要避免 AVX-SSE 转换吗?

【问题讨论】:

  • 你可以随意混合。只需确保您启用了正确的编译器标志以强制所有 SIMD 指令为 VEX 编码。
  • @Mystical,编译器 - gcc 4.7.,标志 -O2 -msse -msse2 -msse4.2 -mavx -mfpmath=sse。这是正确的吗?
  • 是的,没关系。尽管-mavx 是您所需要的。指定任何 SIMD 选项会自动启用它下面的所有选项。
  • @Mystical,我明白不需要 -msse -msse2 -msse4.2 标志吗?
  • 正确。任何带有 AVX 的处理器都保证具有 SSE、SSE2、SSE4.2。

标签: c++ performance sse simd avx


【解决方案1】:

您可以随意混合 SSE 和 AVX 内在函数。

您唯一需要确保的是指定正确的编译器标志以启用 AVX。

  • GCC:-mavx
  • Visual Studio:/arch:AVX

否则将导致代码无法编译 (GCC),或者在 Visual Studio 的情况下,
这种废话:

该标志的作用是强制所有 SIMD 指令使用 VEX 编码,以避免上述问题中描述的状态切换惩罚。

【讨论】:

  • 对齐怎么样? AVX 256 要求数据在 32 字节边界上对齐,而 SSE 需要 16 字节边界。如果混合使用它们,则需要将数据对齐到 32 字节,或者对齐到 16 字节并使用未对齐的 AVX 加载/存储,这比我猜的后一种情况更糟。
  • @plasmacel 对齐是一个完全不同的主题,与 SSE 和 AVX 指令的混合无关。这里的混合只是关于指令本身,而不是它们可能采用的操作数。
【解决方案2】:

我谦虚地请求不同 - 我建议尝试混合 SSE 和 AVX, 请阅读 Mystical 写的链接,它警告不要这样混合(尽管没有足够强调它)。根据 AVX 支持,存在关于不同机器的不同代码路径的问题,因此没有混合 - 在您的情况下,混合非常细粒度并且会具有破坏性(由于微架构实现而导致内部延迟)。

澄清一下 - Mystical 对编译中的 vex 前缀是正确的,没有它你会处于非常糟糕的状态,因为你每次都需要 SSE2AVX 辅助,因为你的 YMM 寄存器的上部不能被忽略(除非明确使用vzeroupper)。但是,即使将 128b AVX 与 256b AVX 混合使用,也会产生更微妙的效果。

我也没有看到在这里使用 SSE 的好处,因为你有一个长循环(比如 N>100)你可以从 AVX 中获得大部分的好处,并在标量代码中完成其余部分到 7 次迭代(你的代码可能仍然需要做 3 次)。与混合 AVX/SSE 相比,性能损失微不足道

关于混合物的更多信息 - http://software.intel.com/sites/default/files/m/d/4/1/d/8/11MC12_Avoiding_2BAVX-SSE_2BTransition_2BPenalties_2Brh_2Bfinal.pdf

【讨论】:

  • 你应该澄清一下。不要混合使用legacy-encoded SSE 和 VEX-encoded AVX。如果您将 SSE intrinsics 与 AVX 编译器标志一起使用,则 SSE 内在函数将编译为 VEX 编码的 SSE。将 VEX 编码的 SSE 与 VEX 编码的 AVX 混合使用非常好。
  • @Mystical:引用 Intel 的优化指南——“除了 MMX 指令,几乎所有传统的 128 位 SSE 指令都具有支持三种操作数语法的 AVX 等效指令”。强调差不多。您是对的,将 AVX256 与 AVX128 混合在一起不应该花费任何费用,因为它会将上部归零,但我仍然会非常小心并检查我所有的旧 SSE 代码是否确实转换正确,并提防声称“您可以随心所欲地混合 SSE 和 AVX 内在函数”。话虽如此,我也看不出有任何理由在上述情况下混合 128b 代码
  • 你能给我一个没有 VEX 编码的 128 位 AVX 等效指令的 128 位 SSE 指令的例子吗?如果它们中的任何一个实际上是性能关键指令或受到状态更改的影响,我会感到惊讶。
  • 我的意思是,如果你为编译器指定 AVX,它会进行 VEX 编码,所以你甚至不需要vzeroupper。当您调用其他不支持 AVX 的模块/编译单元时,您是否需要调用 vzeroupper。同样,当不知道 AVX 的代码调用到使用 AVX 的模块时。 (如果你不这样做,你会为跨模块调用支付一次罚金。这就是它的故意设计方式。)但在同一个模块中,你不需要发出vzeroupper。编译器已经完成了所有的 VEX 编码。
  • 至于您所指的没有 VEX 编码的“缺失”指令——我敢打赌它们可能无关紧要。诸如预取或提取到通用寄存器之类的东西不需要 VEX 编码,因为它们是 2 操作数并且只读取寄存器的底部 128 位。 (因此,无需从使用的任何外部存储中取消高 128 位。)
猜你喜欢
  • 2015-05-19
  • 1970-01-01
  • 2015-08-15
  • 1970-01-01
  • 2013-03-21
  • 1970-01-01
  • 2019-01-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多