代码之家  ›  专栏  ›  技术社区  ›  Vladislav Kogan

为什么gcc在std::vector矢量化方面比clang差得多?

  •  9
  • Vladislav Kogan  · 技术社区  · 3 年前

    考虑以下浮点循环,使用-O3-mavx2-mfma编译

    for (auto i = 0; i < a.size(); ++i) {
        a[i] = (b[i] > c[i]) ? (b[i] * c[i]) : 0;
    }
    

    Clang在矢量化方面做得很好。它使用256位ymm寄存器,并理解vblendps/vandps之间的差异,以获得最佳性能。

    .LBB0_7:
            vcmpltps        ymm2, ymm1, ymm0
            vmulps  ymm0, ymm0, ymm1
            vandps  ymm0, ymm2, ymm0
    

    然而,海湾合作委员会的情况要糟糕得多。出于某种原因,它不会比SSE 128位矢量更好(-mrefer矢量宽度=256不会改变任何东西)。

    .L6:
            vcomiss xmm0, xmm1
            vmulss  xmm0, xmm0, xmm1
            vmovss  DWORD PTR [rcx+rax*4], xmm0
    

    如果用普通数组替换( as in guideline ),gcc确实将其向量化为AVX-ymm。

    int a[256], b[256], c[256];
    auto foo (int *a, int *b, int *c) {
      int i;
      for (i=0; i<256; i++){
        a[i] =  (b[i] > c[i]) ? (b[i] * c[i]) : 0;
      }
    }
    

    然而,我没有找到如何使用可变长度std::vector来实现它。gcc需要什么样的提示才能将std::vector矢量化为AVX?

    Source on Godbolt with gcc 13.1 and clang 14.0.0

    1 回复  |  直到 3 年前
        1
  •  8
  •   3CxEZiVlQ    3 年前

    GCC默认为较旧的CPU体系结构编译。

    背景 -march=native 启用使用256位ymm寄存器。

    .L7:
            vmovups ymm1, YMMWORD PTR [rsi+rax]
            vmovups ymm0, YMMWORD PTR [rdx+rax]
            vcmpps  k1, ymm1, ymm0, 14
            vmulps  ymm2{k1}{z}, ymm1, ymm0
            vmovups YMMWORD PTR [rcx+rax], ymm2
    

    背景 -march=x86-64-v4 允许使用512位zmm寄存器。

    .L7:
            vmovups zmm2, ZMMWORD PTR [rsi+rax]
            vcmpps  k1, zmm2, ZMMWORD PTR [rdx+rax], 14
            vmulps  zmm0{k1}{z}, zmm2, ZMMWORD PTR [rdx+rax]
            vmovups ZMMWORD PTR [rcx+rax], zmm0
    
        2
  •  0
  •   Peter Cordes    3 年前

    不是的 std::vector 这就是问题所在 float 以及GCC通常的严重违约 -ftrapping-math 这本应将FP异常视为可见的副作用,但没有正确地做到这一点,并且错过了一些安全的优化。

    (在这种情况下,源中存在条件FP乘法,因此严格的异常行为可以避免在比较为false的情况下可能引发上溢、下溢、不精确或其他异常。)

    请注意,执行自动矢量化的示例使用 int * args,不是 float* 。如果您将其更改为 浮子* 并且使用相同的编译器选项,它也不会自动向量化,即使使用 float *__restrict a ( https://godbolt.org/z/nPzsf377b )。


    gcc -O3 -mavx2 -mfma -fno-trapping-math 自动矢量化所有3个函数( Godbolt )

    void foo (float *__restrict a, float *__restrict b, float *__restrict c) {
      for (int i=0; i<256; i++){
        a[i] =  (b[i] > c[i]) ? (b[i] * c[i]) : 0;
      }
    }
    
    foo(float*, float*, float*):
            xor     eax, eax
    .L143:
            vmovups ymm2, YMMWORD PTR [rsi+rax]
            vmovups ymm3, YMMWORD PTR [rdx+rax]
            vmulps  ymm1, ymm2, YMMWORD PTR [rdx+rax]
            vcmpltps        ymm0, ymm3, ymm2
            vandps  ymm0, ymm0, ymm1
            vmovups YMMWORD PTR [rdi+rax], ymm0
            add     rax, 32
            cmp     rax, 1024
            jne     .L143
            vzeroupper
            ret
    

    顺便说一句,我推荐 -march=x86-64-v3 用于AVX2+FMA特征级别。这也包括BMI1+BMI2等等。它仍然只是使用 -mtune=generic 我认为,但希望在未来忽略那些只对没有AVX2+FMA+BMI2的CPU重要的调整。

    这个 std::矢量 函数体积更大,因为我们没有使用 float *__restrict a = avec.data(); 或类似于承诺由 std::矢量 控制块(并且大小不知道是矢量宽度的倍数),但无重叠情况下的非清除循环使用相同的 vmulps / vcmpltps / vandps


    另请参见: