使用 AVX 后 SSE 运行缓慢
SSE runs slow after using AVX
我一直在处理的一些 SSE2 和 AVX 代码有一个奇怪的问题。我正在使用 运行 时间 cpu 特征检测的 GCC 构建我的应用程序。目标文件是用每个 CPU 特性的单独标志构建的,例如:
g++ -c -o ConvertSamples_SSE.o ConvertSamples_SSE.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -msse
g++ -c -o ConvertSamples_SSE2.o ConvertSamples_SSE2.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -msse2
g++ -c -o ConvertSamples_AVX.o ConvertSamples_AVX.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -mavx
当我第一次启动该程序时,我发现 SSE2 例程与非 SSE 例程相比速度有很好的提升(大约快 100%)。在我 运行 任何 AVX 例程之后,完全相同的 SSE2 例程现在 运行 慢得多。
有人可以解释一下这可能是什么原因吗?
在 AVX 例程 运行s 之前,所有测试都比 FPU 数学快大约 80-130%,如此处可以看出,在 AVX 例程 运行s 之后,SSE 例程慢得多。
如果我跳过 AVX 测试例程,我永远不会看到这种性能损失。
这是我的 SSE2 例程
void Float_S16(const float *in, int16_t *out, const unsigned int samples)
{
static float ratio = (float)Limits<int16_t>::range() / (float)Limits<float>::range();
static __m128 mul = _mm_set_ps1(ratio);
unsigned int i;
for (i = 0; i < samples - 3; i += 4, in += 4, out += 4)
{
__m128i con = _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(in), mul));
out[0] = ((int16_t*)&con)[0];
out[1] = ((int16_t*)&con)[2];
out[2] = ((int16_t*)&con)[4];
out[3] = ((int16_t*)&con)[6];
}
for (; i < samples; ++i, ++in, ++out)
*out = (int16_t)lrint(*in * ratio);
}
和AVX版本一样。
void Float_S16(const float *in, int16_t *out, const unsigned int samples)
{
static float ratio = (float)Limits<int16_t>::range() / (float)Limits<float>::range();
static __m256 mul = _mm256_set1_ps(ratio);
unsigned int i;
for (i = 0; i < samples - 7; i += 8, in += 8, out += 8)
{
__m256i con = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_load_ps(in), mul));
out[0] = ((int16_t*)&con)[0];
out[1] = ((int16_t*)&con)[2];
out[2] = ((int16_t*)&con)[4];
out[3] = ((int16_t*)&con)[6];
out[4] = ((int16_t*)&con)[8];
out[5] = ((int16_t*)&con)[10];
out[6] = ((int16_t*)&con)[12];
out[7] = ((int16_t*)&con)[14];
}
for(; i < samples; ++i, ++in, ++out)
*out = (int16_t)lrint(*in * ratio);
}
我也 运行 通过 valgrind 没有检测到任何错误。
混合使用 AVX 代码和遗留 SSE 代码会导致性能下降。最合理的解决方案是在AVX代码段之后执行VZEROALL指令,尤其是在执行SSE代码之前。
根据 Intel 的图表,进入或退出状态 C(保存了 AVX 寄存器的上半部分的旧版 SSE)时的惩罚大约为 100 个时钟周期。其他转换只有1个周期:
参考文献:
我一直在处理的一些 SSE2 和 AVX 代码有一个奇怪的问题。我正在使用 运行 时间 cpu 特征检测的 GCC 构建我的应用程序。目标文件是用每个 CPU 特性的单独标志构建的,例如:
g++ -c -o ConvertSamples_SSE.o ConvertSamples_SSE.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -msse
g++ -c -o ConvertSamples_SSE2.o ConvertSamples_SSE2.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -msse2
g++ -c -o ConvertSamples_AVX.o ConvertSamples_AVX.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -mavx
当我第一次启动该程序时,我发现 SSE2 例程与非 SSE 例程相比速度有很好的提升(大约快 100%)。在我 运行 任何 AVX 例程之后,完全相同的 SSE2 例程现在 运行 慢得多。
有人可以解释一下这可能是什么原因吗?
在 AVX 例程 运行s 之前,所有测试都比 FPU 数学快大约 80-130%,如此处可以看出,在 AVX 例程 运行s 之后,SSE 例程慢得多。
如果我跳过 AVX 测试例程,我永远不会看到这种性能损失。
这是我的 SSE2 例程
void Float_S16(const float *in, int16_t *out, const unsigned int samples)
{
static float ratio = (float)Limits<int16_t>::range() / (float)Limits<float>::range();
static __m128 mul = _mm_set_ps1(ratio);
unsigned int i;
for (i = 0; i < samples - 3; i += 4, in += 4, out += 4)
{
__m128i con = _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(in), mul));
out[0] = ((int16_t*)&con)[0];
out[1] = ((int16_t*)&con)[2];
out[2] = ((int16_t*)&con)[4];
out[3] = ((int16_t*)&con)[6];
}
for (; i < samples; ++i, ++in, ++out)
*out = (int16_t)lrint(*in * ratio);
}
和AVX版本一样。
void Float_S16(const float *in, int16_t *out, const unsigned int samples)
{
static float ratio = (float)Limits<int16_t>::range() / (float)Limits<float>::range();
static __m256 mul = _mm256_set1_ps(ratio);
unsigned int i;
for (i = 0; i < samples - 7; i += 8, in += 8, out += 8)
{
__m256i con = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_load_ps(in), mul));
out[0] = ((int16_t*)&con)[0];
out[1] = ((int16_t*)&con)[2];
out[2] = ((int16_t*)&con)[4];
out[3] = ((int16_t*)&con)[6];
out[4] = ((int16_t*)&con)[8];
out[5] = ((int16_t*)&con)[10];
out[6] = ((int16_t*)&con)[12];
out[7] = ((int16_t*)&con)[14];
}
for(; i < samples; ++i, ++in, ++out)
*out = (int16_t)lrint(*in * ratio);
}
我也 运行 通过 valgrind 没有检测到任何错误。
混合使用 AVX 代码和遗留 SSE 代码会导致性能下降。最合理的解决方案是在AVX代码段之后执行VZEROALL指令,尤其是在执行SSE代码之前。
根据 Intel 的图表,进入或退出状态 C(保存了 AVX 寄存器的上半部分的旧版 SSE)时的惩罚大约为 100 个时钟周期。其他转换只有1个周期:
参考文献: