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;
}
restrict
エイリアシングが発生しないことを指定するために使用されるキーワードにより、両方のコード (注意 - ここには OpenMP はありません!) が等しく適切にベクトル化されるはずです。残念ながら、これは GCC < 4.7 には当てはまりません。ループのベクトル化は成功しますが、OpenMP コードをコンパイルするときと同じ理由fun1
でベクトル化に失敗します。fun2
これは、 、、および が指すpar->d
メモリ内に が存在しないことをベクトライザーが証明できないためです。の場合は常にそうとは限りません。次の2 つのケースが考えられます。par->a
par->b
par->c
fun1
d
値引数としてレジスタに渡されます。
d
スタック上の値引数として渡されます。
x64 システムでは、System V ABI により、最初のいくつかの浮動小数点引数が XMM レジスタ (AVX 対応 CPU では YMM) に渡されることが義務付けられています。これがこのケースで渡される方法d
であるため、ポインターがそれを指すことはできません。ループはベクトル化されます。x86 システムでは、ABI は引数がスタックに渡されることを義務付けているためd
、3 つのポインターのいずれかによってエイリアスが作成される可能性があります。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];