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->b
和par->c
指向的内存中。这并不总是与fun1
的情况下,其中两种情况是可能的:
在x64系统上,System V ABI要求在XMM寄存器(启用AVX的CPU上的YMM)中传递前几个浮点参数。这就是d
在这种情况下通过的方式,因此没有指针可以指向它 - 循环被矢量化。在x86系统上,ABI要求将参数传递到堆栈,因此d
可能会被三个指针中的任何一个混淆。事实上,如果指示使用-m32
选项生成32位x86代码,GCC拒绝在fun1
中矢量化循环。
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];
我想你可以在这个问题的[答案](http://stackoverflow.com/a/14717689/771663)中找到合理的信息。 – Massimiliano 2013-02-13 19:52:07
谢谢,介绍了如何在OpenMP中使用SIMD,但它似乎没有解释为什么当我使用OpenMP时,SIMD的工作实现已停止工作。没有一种方法可以同时使用吗? – superbriggs 2013-02-13 19:58:38
这也意味着我只能操作相同数量的位,它们只是在数字之间进行分割。当用GCC做这件事时,我并没有被问到有多少我想要分成一个寄存器。由于我使用的是大学的“超级计算机”,因此我假定硬件有SIMD的额外空间。我如何知道这是否正确? – superbriggs 2013-02-13 20:06:05