6

我一直在努力使我的代码能够被 GCC 自动矢量化,但是,当我包含该-fopenmp标志时,它似乎停止了所有自动矢量化的尝试。我正在使用它ftree-vectorize -ftree-vectorizer-verbose=5来矢量化和监控它。

如果我不包含标志,它会开始给我很多关于每个循环的信息,如果它是矢量化的,为什么不。当我尝试使用该omp_get_wtime()函数时,编译器会停止,因为它无法链接。一旦包含标志,它就会简单地列出每个函数并告诉我它在其中矢量化了 0 个循环。

我已经阅读了其他几个地方提到了这个问题,但他们并没有真正找到任何解决方案: http: //software.intel.com/en-us/forums/topic/295858 http://gcc。 gnu.org/bugzilla/show_bug.cgi?id=46032。OpenMP 是否有自己的处理向量化的方式?我需要明确告诉它吗?

4

4 回答 4

9

GCC 矢量化器有一个缺点,最近的 GCC 版本似乎已经解决了这个问题。在我的测试用例中,GCC 4.7.2 成功地矢量化了以下简单循环:

#pragma omp parallel for schedule(static)
for (int i = 0; i < N; i++)
   a[i] = b[i] + c[i] * d;

同时 GCC 4.6.1 没有并且它抱怨循环包含无法分析的函数调用或数据引用。parallel for矢量化器中的错误是由 GCC 实现循环的方式触发的。当 OpenMP 结构被处理和扩展时,简单的循环代码被转换成类似于这样的东西:

struct omp_fn_0_s
{
    int N;
    double *a;
    double *b;
    double *c;
    double d;
};

void omp_fn_0(struct omp_fn_0_s *data)
{
    int start, end;
    int nthreads = omp_get_num_threads();
    int threadid = omp_get_thread_num();

    // This is just to illustrate the case - GCC uses a bit different formulas
    start = (data->N * threadid) / nthreads;
    end = (data->N * (threadid+1)) / nthreads;

    for (int i = start; i < end; i++)
       data->a[i] = data->b[i] + data->c[i] * data->d;
}

...

struct omp_fn_0_s omp_data_o;

omp_data_o.N = N;
omp_data_o.a = a;
omp_data_o.b = b;
omp_data_o.c = c;
omp_data_o.d = d;

GOMP_parallel_start(omp_fn_0, &omp_data_o, 0);
omp_fn_0(&omp_data_o);
GOMP_parallel_end();

N = omp_data_o.N;
a = omp_data_o.a;
b = omp_data_o.b;
c = omp_data_o.c;
d = omp_data_o.d;

4.7 之前的 GCC 中的矢量化器无法矢量化该循环。这不是 OpenMP 特有的问题。无需任何 OpenMP 代码即可轻松重现它。为了确认这一点,我编写了以下简单测试:

struct fun_s
{
   double *restrict a;
   double *restrict b;
   double *restrict c;
   double d;
   int n;
};

void fun1(double *restrict a,
          double *restrict b,
          double *restrict c,
          double d,
          int n)
{
   int i;
   for (i = 0; i < n; i++)
      a[i] = b[i] + c[i] * d;
}

void fun2(struct fun_s *par)
{
   int i;
   for (i = 0; i < par->n; i++)
      par->a[i] = par->b[i] + par->c[i] * par->d;
}

人们会期望这两个代码(注意 - 这里没有 OpenMP!)应该同样好地矢量化,因为restrict用于指定不会发生混叠的关键字。不幸的是,GCC < 4.7 的情况并非如此——它成功地将循环向量化,但在引用与编译 OpenMP 代码时相同的原因时fun1未能将其向量化。fun2

其原因是矢量化器无法证明par->d不位于 、 和 指向的par->a内存par->bpar->c。情况并非总是如此fun1,可能有两种情况:

  • d在寄存器中作为值参数传递;
  • d在堆栈上作为值参数传递。

在 x64 系统上,System V ABI 要求前几个浮点参数在 XMM 寄存器中传递(启用 AVX 的 CPU 上的 YMM)。这就是d在这种情况下传递的方式,因此没有指针可以指向它 - 循环被矢量化。在 x86 系统上,ABI 要求将参数传递到堆栈上,因此d可能会被三个指针中的任何一个别名。实际上,如果指示使用该选项fun1生成 32 位 x86 代码,则GCC 拒绝将循环向量化。-m32

GCC 4.7 通过插入运行时检查来解决这个问题,确保既d不会也par->d不会出现别名。

摆脱d了无法证明的非混叠,以下 OpenMP 代码由 GCC 4.6.1 向量化:

#pragma omp parallel for schedule(static)
for (int i = 0; i < N; i++)
   a[i] = b[i] + c[i];
于 2013-02-14T14:12:02.153 回答
3

我将尝试简要回答您的问题。

  1. OpenMP 是否有自己的处理向量化的方式?

是的...但是从传入的 OpenMP 4.0 开始。上面发布的链接很好地了解了这个结构。另一方面,当前的 OpenMP 3.1 并没有“意识到” SIMD 概念。因此,在实践中(或者至少在我的经验中)发生的情况是,每当在循环中使用 openmp 工作共享结构时,自动矢量化机制就会被禁止。无论如何,这两个概念是正交的,您仍然可以从两者中受益(参见另一个答案)。

  1. 我需要明确告诉它吗?

恐怕是的,至少目前是这样。我将开始以一种明确矢量化的方式重写正在考虑的循环(即,我将在 Intel 平台上使用内在函数,在 IBM 上使用 Altivec 等等)。

于 2013-02-13T20:26:37.707 回答
1

您在问“为什么启用 OpenMP 时 GCC 不能进行矢量化?”。

看来这可能是 GCC 的一个错误 :) http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46032

否则,OpenMP API 可能会引入阻止自动矢量化的依赖性(控制或数据)。要自动验证,给定的代码必须不依赖数据/控制。使用 OpenMP 可能会导致一些虚假的依赖性。

注意:OpenMP(4.0 之前)是使用线程级并行性,这与 SIMD/矢量化正交。一个程序可以同时使用 OpenMP 和 SIMD 并行性。

于 2013-02-14T01:46:11.037 回答
1

我在搜索有关 gcc 4.9 选项 openmp-simd 的评论时遇到了这篇文章,该选项应该激活 OpenMP 4 #pragma omp simd 而无需激活 omp 并行(线程)。gcc bugzilla pr60117(已确认)显示了 pragma omp 阻止在没有 pragma 的情况下发生的自动矢量化的情况。

即使使用 simd 子句,gcc 也不会对 omp parallel for 进行矢量化(并行区域只能自动矢量化嵌套在 parallel for 下的内循环)。我不知道除了 icc 14.0.2 之外的任何编译器,它可以被推荐用于 simd 的#pragma omp parallel 的实现;对于其他编译器,需要 SSE 内在函数编码来获得这种效果。

在我的测试中,Microsoft 编译器没有在并行区域内执行任何自动矢量化,这表明 gcc 在这种情况下具有明显的优势。

单个循环的组合并行化和矢量化有几个困难,即使是最好的实现。通过将矢量化添加到并行循环中,我很少看到超过 2 倍或 3 倍的加速。例如,使用 AVX 双数据类型的向量化,有效地将块大小减少了 4 倍。典型的实现只能在整个数组对齐的情况下实现对齐的数据块,并且块也是向量宽度的精确倍数. 当块没有全部对齐时,由于不同的对齐方式,存在固有的工作不平衡。

于 2014-03-22T13:07:43.940 回答