性能AVX / SSE组件与内部函数
我只是试图检查优化一些基本例程的最佳方法。 在这种情况下,我尝试了将2个浮点数向量相乘的例子:
void Mul(float *src1, float *src2, float *dst)
{
for (int i=0; i<cnt; i++) dst[i] = src1[i] * src2[i];
};
平原C的实施非常缓慢。 我使用AVX做了一些外部ASM,并尝试使用内部函数。 这些是测试结果(时间越小越好):
ASM: 0.110
IPP: 0.125
Intrinsics: 0.18
Plain C++: 4.0
(使用MSVC 2013,SSE2编译,尝试过英特尔编译器,结果几乎相同)
正如你可以看到我的ASM代码甚至是英特尔性能原语(可能是因为我做了很多分支以确保我可以使用AVX对齐的指令)。 但我个人喜欢使用内在方法,它更容易管理,我认为编译器应该尽最大努力优化所有分支和东西(我的ASM代码在这方面很糟糕,但速度更快)。 所以这里是使用内在函数的代码:
int i;
for (i=0; (MINTEGER)(dst + i) % 32 != 0 && i < cnt; i++) dst[i] = src1[i] * src2[i];
if ((MINTEGER)(src1 + i) % 32 == 0)
{
if ((MINTEGER)(src2 + i) % 32 == 0)
{
for (; i<cnt-8; i+=8)
{
__m256 x = _mm256_load_ps( src1 + i);
__m256 y = _mm256_load_ps( src2 + i);
__m256 z = _mm256_mul_ps(x, y);
_mm256_store_ps(dst + i, z);
};
}
else
{
for (; i<cnt-8; i+=8)
{
__m256 x = _mm256_load_ps( src1 + i);
__m256 y = _mm256_loadu_ps( src2 + i);
__m256 z = _mm256_mul_ps(x, y);
_mm256_store_ps(dst + i, z);
};
};
}
else
{
for (; i<cnt-8; i+=8)
{
__m256 x = _mm256_loadu_ps( src1 + i);
__m256 y = _mm256_loadu_ps( src2 + i);
__m256 z = _mm256_mul_ps(x, y);
_mm256_store_ps(dst + i, z);
};
};
for (; i<cnt; i++) dst[i] = src1[i] * src2[i];
简单:首先到dst对齐到32字节的地址,然后转到检查哪些源被对齐。
一个问题是,开始和结束时的C ++实现都没有使用AVX,除非我在编译器中启用AVX,这是我不想要的,因为这应该只是AVX专业化,但是软件甚至可以在平台上工作, AVX不可用。 可悲的是似乎没有vmovss等指令的内在特性,所以将AVX代码与编译器使用的SSE混合在一起可能是一种惩罚。 不过即使我在编译器中启用了AVX,它仍然没有低于0.14。
任何想法如何优化这个使instrisics达到ASM代码的速度?
使用intrinsics的实现与直接C中的实现不同:例如,如果使用参数Mul(p, p, p+1)
调用函数Mul(p, p, p+1)
会怎么样? 你会得到不同的结果。 纯C版本很慢,因为编译器确保代码完全符合您的要求。
如果您希望编译器根据三个数组不重叠的假设进行优化,则需要明确:
void Mul(float *src1, float *src2, float *__restrict__ dst)
甚至更好
void Mul(const float *src1, const float *src2, float *__restrict__ dst)
(我认为只要在输出指针上有__restrict__
就足够了,尽管将它添加到输入指针也不会有什么伤害)
在使用AVX的CPU上,使用未对齐的加载的惩罚非常小 - 我建议将这个小罚款与您用来检查对齐等的所有额外逻辑进行交易,并且只需一个循环+标量代码即可处理任何残余元素:
for (i = 0; i <= cnt - 8; i += 8)
{
__m256 x = _mm256_loadu_ps(src1 + i);
__m256 y = _mm256_loadu_ps(src2 + i);
__m256 z = _mm256_mul_ps(x, y);
_mm256_storeu_ps(dst + i, z);
}
for ( ; i < cnt; i++)
{
dst[i] = src1[i] * src2[i];
}
更好的是,确保你的缓冲区全部是32字节对齐,然后使用对齐的加载/存储。
请注意,在像这样的循环中执行单个算术运算通常是SIMD的一种不好的方法 - 执行时间将主要由加载和存储决定 - 您应该尝试将此乘法与其他SIMD操作结合起来以减轻加载/存储成本。
链接地址: http://www.djcxy.com/p/72735.html