从SSE切换到AVX的处罚?

Kum*_*ter 4 c++ sse avx sse2

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

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

所以,我有2个功能:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我运行启动计时器,运行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也无法帮助它.

我在这里错过了什么吗?

Nor*_* P. 5

我没有测试你的代码,但是由于你的测试看起来很短,也许你看到了Agner Fog在他的微体系结构手册第101页讨论的浮点热身效应(这适用于Sandy Bridge架构).我引用:

处理器在一段时间内没有看到任何浮点指令时处于冷态.256位向量加法和乘法的延迟最初比理想数字长两个时钟,然后长一个时钟,并且在几百个浮点指令之后,处理器进入温度状态,其中延迟分别为3和5个时钟.吞吐量是冷态下256位向量运算的理想值的一半.这种预热效果对128位向量运算的影响较小.128位向量加法和乘法的等待时间最多比理想值长一个时钟周期,并且在冷态下吞吐量不会降低.