性能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

上一篇: Performance AVX/SSE assembly vs. intrinsics

下一篇: SSE on x86, stack alignment