从SSE切换到AVX的处罚?

时间:2013-07-17 20:12:15

标签: c++ sse avx sse2

我知道从AVX指令切换到SSE指令的现有惩罚,而没有先将所有ymm寄存器的上半部分归零,但在我的机器上的特殊情况下(i7-3939K 3.2GHz),似乎即使我在AVX代码部分之前和之后明确使用_mm256_zeroupper,反之亦然(SSE到AVX)是一个非常大的惩罚。

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

所以,我有两个功能:

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微秒,但XVX版本需要22-23微秒。有点困惑,我挖了一下,我发现如何加速AVX版本,使它们比SSE2版本更快,但它是作弊。我只是在启动计时器之前运行ConvertPcm32FloatToPcm32Fixed,然后启动计时器,再次运行ConvertPcm32FloatToPcm32Fixed,然后转换ConvertPcm32FixedToPcm32Float,停止计时器。好像SSE对AVX有一个巨大的惩罚,如果我首先用试运行“启动”AVX版本,AVX执行时间下降到12微秒,而对SSE等价物做同样的事情只会减少时间微秒到14,使AVX在这里成为边缘赢家,但前提是我作弊。我认为AVX可能不像SSE那样与缓存一样好,但使用_mm_prefetch也没有任何帮助它。

我在这里错过了什么吗?

2 个答案:

答案 0 :(得分:5)

我没有测试你的代码,但是因为你的测试看起来很短,所以你可能会看到Agner Fog在他{{3}的p.101上讨论的浮点热身效应 (这适用于Sandy Bridge架构)。我引用:

  

当处理器没有看到浮动状态时处理器处于冷态   指示一段时间。 256位向量的延迟   加法和乘法最初比两个时钟长两倍   理想的数字,然后一个时钟更长,几百   浮点指令处理器进入暖状态   延迟分别为3和5个时钟。吞吐量是一半   冷态下256位向量运算的理想值。 128位   矢量运算受这种预热效果的影响较小。该   128位向量加法和乘法的延迟为   大多数时钟周期长于理想值和吞吐量   在寒冷的状态下不会减少。

答案 1 :(得分:2)

我的印象是,除非编译器使用VEX指令格式对SSE指令进行编码,正如Paul R所说 - vmulps而不是mulps,命中率很高。

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

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

IACA生成的报告包含以下表示法:

“@ - SSE指令遵循AVX256指令,预计会有数十个周期的惩罚”