为什么GCC不能对这个函数和循环进行向量化?

9

我正在尝试使一个函数启用SIMD并通过函数调用向量化循环。

#include <cmath>

#pragma omp declare simd
double BlackBoxFunction(const double x) {
    return 1.0/sqrt(x);
}

double ComputeIntegral(const int n, const double a, const double b) {
    const double dx = (b - a)/n;
    double I = 0.0;
    #pragma omp simd reduction(+: I)

    for (int i = 0; i < n; i++) {
      const double xip12 = a + dx*(double(i) + 0.5);
      const double yip12 = BlackBoxFunction(xip12);
      const double dI = yip12*dx;
      I += dI; 
  }
  return I;
}

对于上面的代码,如果我使用icpc编译它:

icpc worker.cc -qopenmp -qopt-report=5 -c

优化报告显示该函数和循环均被向量化。但是,如果我尝试使用 g++ 6.5 进行编译:

g++ worker.cc -O3 -fopenmp -fopt-info-vec-missed -funsafe-math-optimizations -c

输出显示note:not vectorized:control flow in loop。note:bad loop form,循环无法矢量化。
如何使用GCC矢量化循环?
编辑:
如果我将函数编写到单独的文件中, worker.cc:
#include "library.h"

double ComputeIntegral(const int n, const double a, const double b) {
    const double dx = (b - a)/n;
    double I = 0.0;
    #pragma omp simd reduction(+: I)

    for (int i = 0; i < n; i++) {
      const double xip12 = a + dx*(double(i) + 0.5);
      const double yip12 = BlackBoxFunction(xip12);
      const double dI = yip12*dx;
      I += dI; 
  }
  return I;
}

library.h:

#ifndef __INCLUDED_LIBRARY_H__
#define __INCLUDED_LIBRARY_H__

#pragma omp declare simd
double BlackBoxFunction(const double x); 

#endif

library.cc 文件:

#include <cmath>

#pragma omp declare simd
double BlackBoxFunction(const double x) {
  return 1.0/sqrt(x);
}

然后我使用GCC编译它:

g++ worker.cc library.cc -O3 -fopenmp -fopt-info-vec-missed -funsafe-math-optimizations -c

它显示:
worker.cc:9:31: note: loop vectorized

但是
library.cc:5:18: note:not vectorized: control flow in loop.
library.cc:5:18: note:bad loop form.

这让我感到困惑。我想知道它是否已经矢量化。


相关的GCC源代码在此:https://github.com/gcc-mirror/gcc/blob/gcc-6_5_0-release/gcc/tree-vect-loop.c#L1310。你可以看到,如果循环中有两个以上的基本块(包括循环机制本身),它会产生控制流警告。我猜这意味着函数调用将循环内容分成了两个或三个块;如果你手动内联`BlackBoxFunction`,它是否能正常工作? - Rup
@Rup 我尝试了,但GCC仍然提示“循环中的控制流”和“错误的循环形式”。 - pangbryant
1个回答

7

在进行一些代码微调后,gcc可以实现向量化:

#include <cmath>

double BlackBoxFunction(const double x) {
    return 1.0/sqrt(x);
}

double ComputeIntegral(const int n, const double a, const double b) {
    const double dx = (b - a)/n;
    double I = 0.0;
    double d_i = 0.0;
    for (int i = 0; i < n; i++) {
      const double xip12 = a + dx*(d_i + 0.5);
      d_i = d_i + 1.0;
      const double yip12 = BlackBoxFunction(xip12);
      const double dI = yip12*dx;
      I += dI; 
  }
  return I;
}

这是使用编译器选项编译的:-Ofast -march=haswell -fopt-info-vec-missed -funsafe-math-optimizations。主循环编译为:

.L7:
    vaddpd  ymm2, ymm4, ymm7
    inc     eax
    vaddpd  ymm4, ymm4, ymm8
    vfmadd132pd     ymm2, ymm9, ymm5
    vsqrtpd ymm2, ymm2
    vdivpd  ymm2, ymm6, ymm2
    vfmadd231pd     ymm3, ymm5, ymm2
    cmp     eax, edx
    jne     .L7

请查看以下Godbolt链接
我删除了#pragma omp ...,因为它们没有改善向量化,但也没有使向量化变差。
请注意,仅将编译器选项从-O3更改为-Ofast就足以启用向量化。然而,使用一个double计数器比每次迭代转换为双精度的int计数器更有效率。
还要注意,向量化报告非常误导人。检查生成的汇编代码以查看向量化是否成功。

你也可以考虑在循环外部使用 double xip12 = a + 0.5*dx; 开始,并使用 xip12 = xip12 + dx; 进行增量,这样会稍微更有效率一些。 - wim
2
选项-Ofast启用了-funsafe-math-optimizations-fno-math-errno。为了对sqrt进行矢量化,您需要使用-fno-math-errno。在矢量化的情况下,加法的顺序与标量情况不同。因此,您还需要-funsafe-math-optimizations,因为浮点加法不是结合律。对于更简单的BlackBoxFunction,只需使用-O2 -funsafe-math-optimizations -ftree-vectorize即可使代码矢量化,请参见此Godbolt链接 - wim
1
值得一提的是,ICPC默认启用快速数学计算,类似于gcc -O3 -ffast-math。OpenMP编译指示可以允许FP缩减自动向量化无需 -ffast-math(只需-O3),但在这种情况下不行。也许如果您仅使用-fno-math-errno以允许sqrt完全内联,则gcc将能够通过启用优化来更改求和结果的顺序来自动向量化缩减。 (由于不同的求和顺序导致不同的舍入,因此FP数学不严格可结合。) - Peter Cordes
1
@Zboson:KNL是“大多数”的例外,但是请看Agner Fog的insn表:SKX的divps吞吐量为标量/xmm:3c,ymm:5c,zmm:10c。但是xmm和ymm的延迟都为11c,而ZMM的延迟为18c。[v]divpd的“double”数字吞吐量分别为4、8和16c。因此,除法器的宽度约为一半,而不是完全流水线化,这是正常的。在KNL上,SSE xmm的延迟为32c/20c吞吐量,而AVX-anything显然是32/32,无论是float还是double。Xeon Phi具有AVX512ER,因为除法吞吐量很差。 - Peter Cordes
2
@Zboson:Peter的评论可以通过此Godbolt链接进行说明。循环向量化使用#pragma omp simd reduction(+: I),但不使用#pragma omp simd(没有reduction(+: I)),除非将-fassociative-math-fno-signed-zeros-fno-trapping-math选项添加到gcc中。 - wim
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接