使用OpenMP停止GCC自动矢量化

时间:2013-02-13 19:27:48

标签: c gcc openmp vectorization

我一直在努力使我的代码能够被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 个答案:

答案 0 :(得分: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没有并且它抱怨说,循环包含无法分析的函数调用或数据引用。 vectoriser中的错误由GCC实现parallel for循环的方式触发。处理和扩展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 - 它成功地对fun1中的循环进行了矢量化,但未能在fun2中引导它,原因与编译OpenMP代码时的原因相同。

原因是矢量化器无法证明par->d不在par->apar->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通过插入运行时检查来确保dpar->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];

答案 1 :(得分:3)

我会尝试简要回答你的问题。

  1. OpenMP是否有自己的处理矢量化的方法?
  2. 是的...但是从传入的OpenMP 4.0开始。上面发布的link提供了对此构造的良好见解。另一方面,当前的OpenMP 3.1并未“了解”SIMD概念。因此,在实践中(或者至少在我的经验中)发生的事情是,无论何时在循环上使用openmp工作共享构造,都禁止自动向量化机制。无论如何,这两个概念是正交的,你仍然可以从这两个概念中受益(参见另一个answer)。

    1. 我是否需要明确告诉它?
    2. 恐怕是的,至少目前是这样。我会开始以一种使矢量化显式化的方式重写所考虑的循环(即我将在Intel平台上使用内在函数,在IBM上使用Altivec等等)。

答案 2 :(得分:1)

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

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

否则,OpenMP API可能会引入阻止自动向量化的依赖项(控件或数据)。要自动转换,给定代码必须是数据/控件依赖性。使用OpenMP可能会导致一些虚假依赖。

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

答案 3 :(得分:1)

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

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

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

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