【问题标题】:Why can GCC not vectorize this function and loop?为什么 GCC 不能向量化这个函数和循环?
【发布时间】:2019-06-06 09:06:01
【问题描述】:

我正在尝试启用一个函数 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

opt-report 显示函数和循环都是矢量化的。 但是,如果我尝试使用 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.

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

【问题讨论】:

  • Relevant GCC source。您可以看到,如果循环内有两个以上的basic blocks,它会引发控制流警告,将循环机制算作其中之一。我猜这意味着函数调用将循环内容分成两个或三个块;如果您手动内联BlackBoxFunction 是否有效?
  • @Rup 我试过了,但 GCC 仍然提示 control flow in loopbad loop form

标签: c++ openmp vectorization simd


【解决方案1】:

在对代码稍作修改后,可以使用 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 link

我删除了#pragma omp ...,因为它们没有改进矢量化,但也没有使矢量化变得更糟。

请注意,仅将编译器选项从 -O3 更改为 -Ofast 是 足以启用矢量化。尽管如此,使用double 计数器比使用int 计数器更有效,int 计数器每次迭代都会转换为双倍。

还请注意,矢量化报告非常具有误导性。检查生成的汇编代码,看向量化是否成功。

【讨论】:

  • 也可以考虑在循环外以double xip12 = a + 0.5*dx;开头,以xip12 = xip12 + dx;递增,效率会稍微高一点。
  • 选项-Ofast 启用-funsafe-math-optimizations-fno-math-errno。为了矢量化sqrt,您需要-fno-math-errno。在矢量化情况下,加法的排序方式与标量情况下不同。因此,您还需要-funsafe-math-optimizations,因为浮点加法不是关联的。使用更简单的BlackBoxFunction,代码已经向量化,只有-O2 -funsafe-math-optimizations -ftree-vectorizesee this Godbolt link
  • @pangbryant:值得一提的是ICPC默认启用快速数学,有点类似于gcc -O3 -ffast-math。 OpenMP pragma 可以允许 FP 减少的自动矢量化没有 -ffast-math(只是-O3),但在这种情况下不是。也许如果你只使用-fno-math-errno 来允许sqrt 完全内联,gcc 将能够自动矢量化减少,这要归功于 pragma 启用优化来改变求和结果的顺序。 (FP 数学不是严格关联的,因为不同求和顺序的舍入不同。)
  • @Zboson:KNL 是“大多数”的例外,但是请看 Agner Fog 的 insn 表:SKX 的divps 吞吐量是标量/xmm:3c,ymm:5c,zmm:10c。但是 xmm 和 ymm 都有 11c 的延迟,而 ZMM 有 18c 的延迟。 double 吞吐量的 double 数字是 4、8 和 16c 吞吐量。所以分隔线大约是一半宽度是正常的,而不是完全流水线。在 KNL 上,SSE xmm 是 32c 延迟/20c 吞吐量,而 AVX-anything 显然是 32/32,无论浮点数/双精度数如何。 Xeon Phi 有 AVX512ER,因为除法吞吐量是垃圾。
  • @Zboson:Peter 的评论由this Godbolt link 说明。循环使用#pragma omp simd reduction(+: I) 进行矢量化,但不使用#pragma omp simd(没有reduction(+: I),除非将-fassociative-math-fno-signed-zeros-fno-trapping-math 选项添加到gcc。
猜你喜欢
  • 2011-12-29
  • 2011-06-28
  • 1970-01-01
  • 2011-11-22
  • 2014-09-01
  • 2018-08-01
  • 1970-01-01
  • 2014-04-28
  • 1970-01-01
相关资源
最近更新 更多