2013-02-13 58 views
6

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

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

我读过一些其他地方的问题已经提到,但他们并没有真正的解决方案:http://software.intel.com/en-us/forums/topic/295858http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46032。 OpenMP有自己的处理矢量化的方法吗?我需要明确告诉它吗?

+0

我想你可以在这个问题的[答案](http://stackoverflow.com/a/14717689/771663)中找到合理的信息。 – Massimiliano 2013-02-13 19:52:07

+0

谢谢,介绍了如何在OpenMP中使用SIMD,但它似乎没有解释为什么当我使用OpenMP时,SIMD的工作实现已停止工作。没有一种方法可以同时使用吗? – superbriggs 2013-02-13 19:58:38

+1

这也意味着我只能操作相同数量的位,它们只是在数字之间进行分割。当用GCC做这件事时,我并没有被问到有多少我想要分成一个寄存器。由于我使用的是大学的“超级计算机”,因此我假定硬件有SIMD的额外空间。我如何知道这是否正确? – superbriggs 2013-02-13 20:06:05

回答

9

GCC vectoriser中存在一个缺点,它似乎在最近的GCC版本中已经得到解决。在我的测试情况,GCC 4.7.2 vectorises成功以下简单的循环:

在同一时间GCC 4.6.1不和它抱怨,环路包含函数调用或无法分析的数据的引用。矢量化程序中的错误由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; 

在GCC的vectoriser 4.7之前未能vectorise该循环。这不是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的!)应该vectorise因为用于指定不走样可能发生的restrict关键字的同样出色。不幸的是,GCC < 4.7的情况并非如此 - 它成功地将fun1中的循环向量化,但无法引用fun2中引用与编译OpenMP代码时相同的原因。

原因是矢量器无法证明par->d不在par->a,par->bpar->c指向的内存中。这并不总是与fun1的情况下,其中两种情况是可能的:

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

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

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]; 
+0

伟大的答案。但是你能否多说一些:“这只是为了说明情况 - GCC使用了一些不同的公式”。 GCC使用什么公式? – 2014-03-23 13:28:52

+0

@Zboson,我可以把它粘贴在这里(丑陋),但是你更愿意运行'gcc -fdump-tree-all -fopenmp foo.c'并且检查OpenMP扩展后的AST,通常位于'foo。 c.015t.ompexp'。不同之处在于GCC通过给第一个'r'线程增加一个迭代来分配'r = N%num_threads'分支的余数。 – 2014-03-23 14:40:22

3

我会尽量简要回答你的问题。

  1. OpenMP有自己的处理vectorisation的方法吗?

是的......但是从传入的OpenMP 4.0开始。上面发布的link提供了一个很好的洞察这个结构。另一方面,目前的OpenMP 3.1并不知道SIMD的概念。因此,在实践中(或者至少在我的经验中)会发生什么情况是,只要在循环中使用openmp工作共享结构,就会禁止自动向量化机制。无论如何,这两个概念是正交的,你仍然可以从两个方面受益(见其他answer)。

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

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

+0

非常感谢。你的第一个链接给出了函数'VECTOR_ADD'。我已经读过,使用一个正常大小的寄存器来做到这一点,因此只允许小数目被矢量化。我知道我的硬件有特定的寄存器来处理SIMD,所以不会发生。有没有办法让OpenMP使用这个寄存器?我需要使用这些功能,考虑到GCC之前为我做的一切吗?我不明白为什么OpenMP会阻止这种形式的工作。你的第二个链接说,他们可以一起工作,但不是我会如何实现这一目标。再一次非常感谢你。 – superbriggs 2013-02-13 20:34:59

+0

主要思想是OpenMP不能意识到SIMDization,因为您在VECTOR_ADD中处理它。我从来没有使用3Dnow,但在Intel平台上,您可以使用[intrinsics](http://software.intel.com/zh-cn/articles/how-to-use-intrinsics)来明确地向量化代码。主要缺点是要么丢失可移植性(因为内在函数不能在其他平台上工作)或可读性/可维护性(由于条件编译)。 – Massimiliano 2013-02-13 20:42:14

+0

对于这个项目的可维护性和可移植性并不重要。我目前不使用VECTOR_ADD,我只是简单地把它放在一个循环中,让GCC可以看到发生了什么并自动引导它。 – superbriggs 2013-02-13 21:26:48

1

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

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

一个错误否则,OpenMP的API可以引入依赖(控制或数据),以防止自动矢量。为了自动转换,给定的代码必须是数据/控制无关的。有可能使用OpenMP可能会导致一些虚假依赖。

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

1

我碰到这个帖子跑,同时寻找有关GCC 4.9选项评论openmp- simd,它应该激活OpenMP 4 #pragma omp simd而不激活omp parallel(线程)。 gcc bugzilla pr60117(已确认)显示了一种情况,其中,编译指示omp可防止发生没有编译指示的自动矢量化。

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

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

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