代码之家  ›  专栏  ›  技术社区  ›  fabian

(为什么?)simpe循环不是矢量化的,而且比SIMD计算慢吗?

  •  3
  • fabian  · 技术社区  · 2 年前

    (为什么?)编译器不使用SIMD指令进行简单循环计算和吗 -03 and -march=native ?

    考虑以下两个函数:

    float sum_simd(const std::vector<float>& vec) {
        __m256 a{0.0};
        for (std::size_t i = 0; i < vec.size(); i += 8) {
            __m256 tmp = _mm256_loadu_ps(&vec[i]);
            a = _mm256_add_ps(tmp, a);
        }
        float res{0.0};
        for (size_t i = 0; i < 8; ++i) {
            res += a[i];
        }
        return res;
    }
    
    float normal_sum(const std::vector<float>& vec) {
        float sum{0};
        for (size_t i = 0; i < vec.size(); ++i) {
            sum += vec[i];
        }
        return sum;
    }
    

    编译器似乎将总和转换为:

    vaddps  ymm0, ymm0, ymmword ptr [rax + 4*rdx]
    

    和

    vaddss  xmm0, xmm0, dword ptr [rcx + 4*rsi]
    vaddss  xmm0, xmm0, dword ptr [rcx + 4*rsi + 4]
    vaddss  xmm0, xmm0, dword ptr [rcx + 4*rsi + 8]
    vaddss  xmm0, xmm0, dword ptr [rcx + 4*rsi + 12]
    vaddss  xmm0, xmm0, dword ptr [rcx + 4*rsi + 16]
    vaddss  xmm0, xmm0, dword ptr [rcx + 4*rsi + 20]
    vaddss  xmm0, xmm0, dword ptr [rcx + 4*rsi + 24]
    vaddss  xmm0, xmm0, dword ptr [rcx + 4*rsi + 28]
    

    当我在我的机器上运行这个程序时,我从SIMD总和中获得了显著的加速(约为10倍)。Godbolt也是如此。看见 here 用于代码。

    我用GCC 13和Clang 17编译了这个程序,并使用了选项 -O3 -march=native .

    为什么是函数 normal_sum 速度较慢且未完全矢量化?是否需要指定其他编译器选项?

    1 回复  |  直到 2 年前
        1
  •  2
  •   Jérôme Richard    2 年前

    为什么函数normal_sum较慢且未完全矢量化?是否需要指定其他编译器选项?

    对 -ffastmath 解决了这个问题( see on Godbolt ).以下是带有此附加标志的主循环:

    .L10:
            vaddps  ymm1, ymm1, YMMWORD PTR [rax]     ;     <---------- vectorized
            add     rax, 32
            cmp     rcx, rax
            jne     .L10
    

    但是,请注意 -ffastmath 是几个更具体的标志的组合。其中一些 可能非常危险 例如 -funsafe-math-optimizations 和 -ffinite-math-only 可以使用无穷大破坏现有代码或降低其精度。事实上,有些代码像 Kahan summation algorithm 要求编译器不要假定浮点运算是关联的( -ffast-math 确实如此)。 有关这方面的更多信息,请阅读帖子 What does gcc's ffast-math actually do? .

    代码不能自动向量化的主要原因 -ffastmath 只是因为像求和这样的浮点运算 不关联 (即。 (a+b)+c != a+(b+c) ).因此,编译器无法对浮点加法的长链进行重新排序。请注意,有一个专门用来改变这种行为的标志( -fassociative-math ),但通常仅对代码进行自动向量化是不够的( this is the case here ).需要使用标志的组合(的子集 -ffast数学 )以实现关于目标编译器(以及可能的版本)的自动向量化。

    请注意,在Clang上,向量化代码的一种简单的独立于体系结构的方法是使用 OpenMP 。要做到这一点,您需要添加行 #pragma omp simd 在循环和编译标志之前 -fopenmp-simd . See on Godbolt 不幸的是,该解决方案还不能在GCC上工作(AFAIK,这是因为到目前为止,GCC后端优化步骤忽略了OpenMP SIMD杂注)。