我一直在努力使我的代码能够被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有自己的处理矢量化的方法吗?我需要明确告诉它吗?
答案 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->a
,par->b
和par->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];
答案 1 :(得分:3)
我会尝试简要回答你的问题。
答案 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倍。典型的实现只能在整个阵列对齐的情况下实现对齐的数据块,并且块也是矢量宽度的精确倍数。当块未全部对齐时,由于对齐的变化,存在固有的工作不平衡。