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