【问题标题】:Penalty for switching from SSE to AVX?从 SSE 切换到 AVX 会受到惩罚吗?
【发布时间】:2013-07-16 13:40:06
【问题描述】:

我知道在没有先将所有 ymm 寄存器的上半部分清零的情况下从 AVX 指令切换到 SSE 指令的现有惩罚,但在我的机器(i7-3939K 3.2GHz)上的特殊情况下,似乎即使我在 AVX 代码部分之前和之后明确使用了 _mm256_zeroupper,也会对相反的方式(SSE 到 AVX)造成非常大的惩罚。

我已经编写了用于在 32768 个元素宽的 2 个缓冲区上转换 32 位浮点数和 32 位定点整数的函数。我将 SSE2 内在版本直接移植到 AVX 以一次执行 8 个元素,而不是 SSE 的 4 个元素,期望看到显着的性能提升,但不幸的是,相反的事情发生了。

所以,我有两个功能:

void ConvertPcm32FloatToPcm32Fixed(int32* outBuffer, const float* inBuffer, uint sampleCount, bool bUseAvx)
{
    const float fScale = (float)(1U<<31);

    if (bUseAvx)
    {
        _mm256_zeroupper();
        const __m256 vScale = _mm256_set1_ps(fScale);
        const __m256 vVolMax = _mm256_set1_ps(fScale-1);
        const __m256 vVolMin = _mm256_set1_ps(-fScale);

        for (uint i = 0; i < sampleCount; i+=8)
        {
            const __m256 vIn0 = _mm256_load_ps(inBuffer+i); // Aligned load
            const __m256 vVal0 = _mm256_mul_ps(vIn0, vScale);
            const __m256 vClamped0 = _mm256_min_ps( _mm256_max_ps(vVal0, vVolMin), vVolMax );
            const __m256i vFinal0 = _mm256_cvtps_epi32(vClamped0);
            _mm256_store_si256((__m256i*)(outBuffer+i), vFinal0); // Aligned store
        }
        _mm256_zeroupper();
    }
    else
    {
        const __m128 vScale = _mm_set1_ps(fScale);
        const __m128 vVolMax = _mm_set1_ps(fScale-1);
        const __m128 vVolMin = _mm_set1_ps(-fScale);

        for (uint i = 0; i < sampleCount; i+=4)
        {
            const __m128 vIn0 = _mm_load_ps(inBuffer+i); // Aligned load
            const __m128 vVal0 = _mm_mul_ps(vIn0, vScale);
            const __m128 vClamped0 = _mm_min_ps( _mm_max_ps(vVal0, vVolMin), vVolMax );
            const __m128i vFinal0 = _mm_cvtps_epi32(vClamped0);
            _mm_store_si128((__m128i*)(outBuffer+i), vFinal0); // Aligned store
        }
    }
}

void ConvertPcm32FixedToPcm32Float(float* outBuffer, const int32* inBuffer, uint sampleCount, bool bUseAvx)
{
    const float fScale = (float)(1U<<31);

    if (bUseAvx)
    {
        _mm256_zeroupper();
        const __m256 vScale = _mm256_set1_ps(1/fScale);

        for (uint i = 0; i < sampleCount; i+=8)
        {
            __m256i vIn0 = _mm256_load_si256(reinterpret_cast<const __m256i*>(inBuffer+i)); // Aligned load
            __m256 vVal0 = _mm256_cvtepi32_ps(vIn0);
            vVal0 = _mm256_mul_ps(vVal0, vScale);
            _mm256_store_ps(outBuffer+i, vVal0); // Aligned store
        }
        _mm256_zeroupper();
    }
    else
    {
        const __m128 vScale = _mm_set1_ps(1/fScale);

        for (uint i = 0; i < sampleCount; i+=4)
        {
            __m128i vIn0 = _mm_load_si128(reinterpret_cast<const __m128i*>(inBuffer+i)); // Aligned load
            __m128 vVal0 = _mm_cvtepi32_ps(vIn0);
            vVal0 = _mm_mul_ps(vVal0, vScale);
            _mm_store_ps(outBuffer+i, vVal0); // Aligned store
        }
    }
}

所以我启动了一个计时器,运行 ConvertPcm32FloatToPcm32Fixed 然后 ConvertPcm32FixedToPcm32Float 直接转换回来,结束计时器。函数的 SSE2 版本总共执行 15-16 微秒,但 AVX 版本需要 22-23 微秒。有点困惑,我进一步挖掘,我发现了如何加速 AVX 版本,以便它们比 SSE2 版本更快,但这是作弊。我只是在启动计时器之前运行 ConvertPcm32FloatToPcm32Fixed,然后启动计时器,然后再次运行 ConvertPcm32FloatToPcm32Fixed,然后运行 ​​ConvertPcm32FixedToPcm32Float,停止计时器。好像 SSE 对 AVX 有很大的惩罚,如果我首先通过试运行“启动”AVX 版本,AVX 执行时间会下降到 12 微秒,而对 SSE 等效项做同样的事情只会将时间缩短一个微秒到 14,使 AVX 成为这里的边际赢家,但前提是我作弊。我认为也许 AVX 在缓存中的表现不如 SSE,但使用 _mm_prefetch 也无济于事。

我错过了什么吗?

【问题讨论】:

  • 你能提供一个SSCCE吗?
  • 对于 SSE 代码,您是使用旧的 SSE(破坏性)还是新的 SSE(非破坏性)指令?我的理解是 AVX-SSE 切换惩罚只适用于前者?
  • 旧 SSE 实际上具有破坏性。但是从阅读文档来看,无论如何这都不重要,因为我在去 AVX 而不是从 AVX 时受伤了。
  • 已添加 C++ 代码。切换到 AVX 似乎确实会受到惩罚,因为我只是通过在计时器之前运行 SSE2 版本来缓存缓冲区,然后在之后运行 AVX 来测试它,但性能损失仍然存在。
  • 如果您使用 -mavx 编译 SSE 代码,那么它应该使用新的(非破坏性)SSE 指令,例如VMULPS 而不是 MULPS - 上面的代码是您实际用于测试的代码,还是真正的代码有单独的模块编译有/没有 -mavx

标签: c++ sse avx sse2


【解决方案1】:

我没有测试您的代码,但由于您的测试看起来很短,也许您看到了 Agner Fog 在其microarchitecture manual 的第 101 页讨论的 浮点预热效果 (这适用于 Sandy Bridge 架构)。我引用:

处理器在没有看到任何浮动时处于冷态 点指令一会儿。 256 位向量的延迟 加法和乘法最初是两个时钟比 理想的数字,然后再长一个时钟,在几百个之后 浮点指令处理器进入暖状态,其中 延迟分别为 3 和 5 个时钟。吞吐量是一半 冷态下 256 位向量运算的理想值。 128 位 矢量运算受这种热身效应的影响较小。这 128 位向量加法和乘法的延迟为 大多数比理想值长一个时钟周期,吞吐量 冷态下不还原。

【讨论】:

  • 谢谢,罗伯特。这似乎很符合症状,但我想知道“一段时间”到底有多长。在我的例子中,转换代码每 21 毫秒执行一次,每次都具有相同的效果。可能值得一提的是,我也可以在完全不同(特殊)的 CPU 架构和也具有 AVX 的编译器上运行相同的测试,而且我根本没有因为 AVX 而受到这些惩罚,但我不确定我是否在可以说这个系统是什么。
【解决方案2】:

我的印象是,除非编译器使用 VEX 指令格式对 SSE 指令进行编码,就像 Paul R 所说的 - vmulps 而不是 mulps,否则会产生巨大的影响。

在优化小段时,我倾向于将英特尔的这个好工具与一些优秀的基准测试结合使用

https://software.intel.com/en-us/articles/intel-architecture-code-analyzer

IACA 生成的报告包含以下符号:

"@ - SSE 指令跟在一条 AVX256 指令之后,预计会有几十个周期的惩罚"

【讨论】:

    猜你喜欢
    • 2017-10-08
    • 2013-09-14
    • 2017-11-22
    • 2015-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-31
    相关资源
    最近更新 更多