性能 AVX/SSE 程序集与内在函数

mrz*_*cek 2 c++ assembly sse intrinsics avx

我只是想检查优化一些基本例程的最佳方法。在这种情况下,我尝试了将 2 个浮点向量相乘的非常简单的示例:

void Mul(float *src1, float *src2, float *dst)
{
    for (int i=0; i<cnt; i++) dst[i] = src1[i] * src2[i];
};
Run Code Online (Sandbox Code Playgroud)

普通的 C 实现非常慢。我使用 AVX 做了一些外部 ASM,也尝试使用内在函数。这些是测试结果(时间越小越好):

ASM: 0.110
IPP: 0.125
Intrinsics: 0.18
Plain C++: 4.0
Run Code Online (Sandbox Code Playgroud)

(使用 MSVC 2013、SSE2 编译,尝试过 Intel 编译器,结果几乎相同)

正如您所看到的,我的 ASM 代码甚至击败了 Intel Performance Primitives(可能是因为我做了很多分支以确保我可以使用 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];
Run Code Online (Sandbox Code Playgroud)

简单:首先到达 dst 与 32 字节对齐的地址,然后分支以检查哪些源对齐。

一个问题是,C++ 实现在开头和结尾都没有使用 AVX,除非我在编译器中启用 AVX,这是我不想要的,因为这应该只是 AVX 专业化,但该软件甚至应该在平台上运行, AVX 不可用的地方。遗憾的是,vmovss 等指令似乎没有内在函数,因此将 AVX 代码与编译器使用的 SSE 混合可能会受到惩罚。但是,即使我在编译器中启用了 AVX,它仍然没有低于 0.14。

任何想法如何优化它以使内部达到 ASM 代码的速度?

小智 5

您使用内在函数的实现与您在直接 C 中的实现不同:例如,如果您的函数是使用参数调用的Mul(p, p, p+1)怎么办?你会得到不同的结果。纯 C 版本很慢,因为编译器确保代码完全按照你说的做。

如果您希望编译器基于三个数组不重叠的假设进行优化,则需要明确说明:

void Mul(float *src1, float *src2, float *__restrict__ dst)
Run Code Online (Sandbox Code Playgroud)

甚至更好

void Mul(const float *src1, const float *src2, float *__restrict__ dst)
Run Code Online (Sandbox Code Playgroud)

(我认为__restrict__只在输出指针上就足够了,尽管将它添加到输入指针也没有坏处)