【发布时间】: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?