SSE和AVX指令混合使用

6

除了SSE-copy、AVX-copy和std::copy的性能之外,假设我们需要以以下方式向量化某些循环: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转换吗?


4
你可以随意混合使用,只需确保启用了正确的编译器标志以强制所有SIMD指令为VEX编码。 - Mysticial
@Mystical,编译器 - gcc 4.7.,标志 -O2 -msse -msse2 -msse4.2 -mavx -mfpmath=sse。这正确吗? - gorill
2
没问题。虽然-mavx就足够了。指定任何SIMD选项都会自动启用其下面的所有选项。 - Mysticial
@Mystical,我理解不需要使用-msse -msse2 -msse4.2标志吗? - gorill
3
正确。任何具备AVX指令集的处理器都保证同时具备SSE、SSE2、SSE4.2指令集。 - Mysticial
2个回答

10
你可以任意混合使用SSE和AVX指令集。唯一需要确保的是要指定正确的编译器标志以启用AVX。
  • GCC:-mavx
  • Visual Studio:/arch:AVX
未能这样做将导致代码无法编译(GCC),或在Visual Studio的情况下导致性能低下的问题,如下所述: 该标志的作用是强制所有SIMD指令使用VEX编码,以避免上述问题中描述的状态切换惩罚。

对齐方面怎么处理?AVX 256 要求数据在32字节边界上对齐,而SSE则需要16字节边界。如果你混合使用它们,你需要将数据对齐到32字节,或者对齐到16字节并使用未对齐的AVX加载/存储,后一种情况比前一种情况更糟糕。 - plasmacel
@plasmacel 对齐是一个完全不同的话题,与 SSE 和 AVX 指令混合无关。这里混合只涉及指令本身,而不涉及其可能采取的操作数。 - Mysticial

1

我谨慎地持不同意见 - 我建议尝试不要混合使用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


2
你应该澄清一下。不要混合使用遗留编码的SSE和VEX编码的AVX。如果你在AVX编译器标志下使用SSE 内嵌函数,那么SSE内嵌函数将会编译成VEX编码的SSE。但是,混合使用VEX编码的SSE和VEX编码的AVX是完全可以的。 - Mysticial
@Mystical: 引用英特尔的优化指南 -“除MMX指令外,几乎所有传统的128位SSE指令都有支持三操作数语法的AVX等效指令。”强调几乎。你是对的,混合AVX256和AVX128不应该花费任何代价,因为它将上部分清零,但我仍然要非常小心,检查我的所有传统SSE代码是否被正确转换,并谨慎对待类似“您可以随意混合SSE和AVX内在函数”的说法。话虽如此,在上述情况下,我也没有看到混合使用128位代码的任何理由。 - Leeor
1
你能给我一个128位SSE指令的例子,它没有VEX编码的128位AVX等效指令吗?如果有任何这样的指令实际上是性能关键指令或受状态更改影响的话,我会感到惊讶。 - Mysticial
我记得有些事情,我得去找一下。但要记住,状态变化不仅是数据依赖块,它们会导致阻塞一段时间的辅助原因。请参见此处 - http://software.intel.com/sites/default/files/m/d/4/1/d/8/11MC12_Avoiding_2BAVX-SSE_2BTransition_2BPenalties_2Brh_2Bfinal.pdf ,顺便说一句 - 此链接还指出,您所提供的编译器解决方案的一部分依赖于在函数开头添加vzeroupper,这意味着不能在函数内进行混合。 - Leeor
2
我想说的是,如果你向编译器指定AVX,它将进行VEX编码,因此你甚至不需要 vzeroupper。只有在调用其他未支持AVX的模块/编译单元时,才需要调用 vzeroupper。同样,当不支持AVX的代码调用使用AVX的模块时也需要。 (如果你不这样做,你会为跨模块调用支付一次代价。这就是它的设计意图。)但是在同一个模块中,你不需要发出 vzeroupper。编译器已经完成了所有的VEX编码。 - Mysticial
2
关于你所提到的那些没有VEX编码的“缺失”指令 - 我敢打赌它们可能并不重要。像预取或提取到通用寄存器之类的东西不需要VEX编码,因为它们是2操作数,并且只读取寄存器的底部128位。(因此没有必要从任何外部存储中取出上面的128位。) - Mysticial

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接