OpenMP は、実際には pthread にコンパイルされるマクロのセットにすぎないことを理解しています。残りのコンパイルが行われる前に pthread コードを確認する方法はありますか? コンパイルにはGCCを使用しています。
3 に答える
まず、OpenMP は単純なマクロのセットではありません。pthread のようなコードへの単純な変換に見えるかもしれませんが、OpenMP はランタイム サポートを含め、それ以上のものを必要とします。
質問に戻りますが、少なくとも GCC では、GCC の OpenMP 実装がコンパイラのバックエンド (またはミドルエンド) で行われるため、pthreaded コードを見ることはできません。変換は IR (中間表現) レベルで行われます。そのため、プログラマーの観点からは、コードが実際にどのように変換されるかを確認するのは容易ではありません。
ただし、いくつかの参照があります。
(1) インテルのエンジニアは、インテル C/C++ コンパイラーでの OpenMP の実装の優れた概要を提供しました。
http://www.drdobbs.com/parallel/how-do-openmp-compilers-work-part-1/226300148
http://www.drdobbs.com/parallel/how-do-openmp-compilers-work-part-2/226300277
(2) GCC の OpenMP の実装をご覧ください。
https://github.com/mirrors/gcc/tree/master/libgomp
libgomp.h
pthread を使用し、loop.c
並列ループ構造の実装が含まれています。
OpenMP は、マクロではなく、一連のコンパイラディレクティブです。C/C++ では、これらのディレクティブは#pragma
拡張メカニズムで実装されますが、Fortran では特別にフォーマットされたコメントとして実装されます。これらのディレクティブは、シリアル コードをパラレル コードに変換するために特定のコード変換を実行するようにコンパイラに指示します。
純粋な pthreads コードへの変換として OpenMP を実装することは可能ですが、これが行われることはめったにありません。通常、OpenMP メカニズムの大部分は、コンパイラ スイートの一部として提供される別のランタイム ライブラリに組み込まれています。GCC の場合、これはlibgomp
. これは、OpenMP コンストラクトを簡単に実装するために使用される一連の高レベル関数を提供します。また、これはコンパイラの内部にあり、ユーザー コードで使用するためのものではありません。つまり、ヘッダー ファイルは提供されていません。
GCC を使用すると、OpenMP 変換後にコードがどのように見えるかを示す疑似コード表現を取得できます。オプションを指定する必要があり-fdump-tree-all
ます。これにより、コンパイラは各コンパイル単位に対して多数の中間ファイルを吐き出します。最も興味深いのはfilename.017t.ompexp
(これは GCC 4.7.1 からのもので、他の GCC バージョンでは番号が異なる可能性がありますが、拡張子は引き続き.ompexp
. このファイルには、OpenMP コンストラクトが下げられ、適切な実装に展開された後のコードの中間表現が含まれています。
として保存された次の C コードの例を考えてみましょうfun.c
。
void fun(double *data, int n)
{
#pragma omp parallel for
for (int i = 0; i < n; i++)
data[i] += data[i]*data[i];
}
の内容fun.c.017t.ompexp
は次のとおりです。
fun (double * data, int n)
{
...
struct .omp_data_s.0 .omp_data_o.1;
...
<bb 2>:
.omp_data_o.1.data = data;
.omp_data_o.1.n = n;
__builtin_GOMP_parallel_start (fun._omp_fn.0, &.omp_data_o.1, 0);
fun._omp_fn.0 (&.omp_data_o.1);
__builtin_GOMP_parallel_end ();
data = .omp_data_o.1.data;
n = .omp_data_o.1.n;
return;
}
fun._omp_fn.0 (struct .omp_data_s.0 * .omp_data_i)
{
int n [value-expr: .omp_data_i->n];
double * data [value-expr: .omp_data_i->data];
...
<bb 3>:
i = 0;
D.1637 = .omp_data_i->n;
D.1638 = __builtin_omp_get_num_threads ();
D.1639 = __builtin_omp_get_thread_num ();
...
<bb 4>:
... this is the body of the loop ...
i = i + 1;
if (i < D.1644)
goto <bb 4>;
else
goto <bb 5>;
<bb 5>:
<bb 6>:
return;
...
}
簡潔にするために、出力の大部分を省略しました。これは厳密には C コードではありません。これは、プログラム フローの C ライクな表現です。<bb N>
いわゆる基本ブロック- ステートメントのコレクションで、プログラムのワークフローで単一のブロックとして扱われます。最初に目にするのは、並列領域が別の関数に抽出されることです。これは珍しいことではありません。ほとんどの OpenMP 実装は、多かれ少なかれ同じコード変換を行います。また、コンパイラがやのlibgomp
ような関数への呼び出しを挿入することも観察できます。これらは、ブートストラップに使用され、並列領域の実行を終了します (プレフィックスは後で削除されます)。内部にはループがあり、実装されていますGOMP_parallel_start
GOMP_parallel_end
__builtin_
fun._omp_fn.0
for
<bb 4>
(ループ自体も展開されることに注意してください)。また、すべての共有変数は、並列領域の実装に渡される特別な構造に入れられます。<bb 3>
現在のスレッドが動作する反復の範囲を計算するコードが含まれています。
まあ、完全な C コードではありませんが、これはおそらく GCC から取得できる最も近いものです。
私はopenmpでそれをテストしていません。ただし、コンパイラ オプション-E
は、前処理後にコードを提供する必要があります。