6

コードを 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 には、ベクトル化を処理する独自の方法がありますか? 明示的に伝える必要がありますか?

4

4 に答える 4

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 は分析できない関数呼び出しまたはデータ参照がループに含まれていると文句を言いません。ベクトライザのバグは、parallel forGCC によるループの実装方法によって引き起こされます。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->apar->bpar->cfun1

  • 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];
于 2013-02-14T14:12:02.153 に答える
3

私はあなたの質問に簡単に答えようとします。

  1. OpenMP には、ベクトル化を処理する独自の方法がありますか?

はい...ただし、次の OpenMP 4.0 から開始します。上記のリンクは、この構造に関する優れた洞察を提供します。一方、現在の OpenMP 3.1 は SIMD の概念を「認識」していません。したがって、実際に (または、少なくとも私の経験では) 起こることは、openmp ワークシェアリング構造がループで使用されるたびに、自動ベクトル化メカニズムが抑制されることです。とにかく、2 つの概念は直交しており、両方の恩恵を受けることができます (この他の回答を参照してください)。

  1. 明示的に伝える必要がありますか?

はい、少なくとも現時点ではそうです。ベクトル化を明示的にする方法で、検討中のループの書き直しを開始します (つまり、Intel プラットフォームでは組み込み関数を使用し、IBM では Altivec などを使用します)。

于 2013-02-13T20:26:37.707 に答える
1

「OpenMP が有効になっているときに GCC がベクトル化を実行できないのはなぜですか?」と尋ねています。

これは GCC のバグのようです :) http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46032

そうしないと、OpenMP API が自動ベクトル化を妨げる依存関係 (コントロールまたはデータ) を導入する可能性があります。自動 Vertorize するには、特定のコードがデータ/コントロールの依存関係から解放されている必要があります。OpenMP を使用すると、誤った依存関係が発生する可能性があります。

注: OpenMP (4.0 より前) は、SIMD/ベクトル化と直交するスレッドレベルの並列処理を使用することになっています。プログラムは、OpenMP 並列処理と SIMD 並列処理の両方を同時に使用できます。

于 2013-02-14T01:46:11.037 に答える
1

gcc 4.9 オプション openmp-simd に関するコメントを探しているときに、この投稿に出くわしました。このオプションは、omp 並列 (スレッド化) をアクティブ化せずに OpenMP 4 #pragma omp simd をアクティブ化する必要があります。gcc bugzilla pr60117 (確認済み) は、プラグマ omp がプラグマなしで発生した自動ベクトル化を防止するケースを示しています。

gcc は、simd 句を使用しても omp parallel for をベクトル化しません (並列領域は、parallel for の下にネストされた内側のループのみを自動ベクトル化できます)。#pragma omp parallel for simd; の実装に推奨される icc 14.0.2 以外のコンパイラを知りません。他のコンパイラでは、この効果を得るために SSE 組み込みコーディングが必要になります。

私のテストでは、Microsoft コンパイラは並列領域内で自動ベクトル化を実行しません。このような場合、gcc の明らかな優位性が示されています。

単一ループの並列化とベクトル化を組み合わせると、最良の実装であっても、いくつかの問題があります。並列ループにベクトル化を追加することで、2 倍または 3 倍以上のスピードアップが見られることはめったにありません。たとえば、AVX の double データ型を使用したベクトル化は、効果的にチャンク サイズを 4 分の 1 に削減します。一般的な実装では、配列全体が整列され、チャンクもベクトル幅の正確な倍数である場合にのみ、整列されたデータ チャンクを実現できます。 . チャンクがすべて整列されていない場合、さまざまな整列が原因で固有の作業の不均衡が生じます。

于 2014-03-22T13:07:43.940 に答える