私は多くの記事と OpenACC のマニュアルを調べていますが、これら 2 つの構造の主な違いはまだわかりません。
3 に答える
kernels
ディレクティブはより一般的なケースであり、以前に GPU (CUDA など) カーネルを作成したことがある場合は、おそらく考えられるケースです。 kernels
コンパイラにコードの一部を処理するように指示し、任意の「次元」の任意の数の「カーネル」を生成して順番に実行し、コードの特定のセクションをアクセラレータに並列化/オフロードします。このparallel
構造により、コンパイラーがアクセラレーターで作業を構築する方法をより細かく制御できます (たとえば、並列化の特定の次元を指定するなど)。たとえば、ワーカーとギャングの数は通常、parallel
ディレクティブの一部として一定です (通常、基になる「カーネル」は 1 つだけであるため)。kernels
この特定の質問の適切な扱いは、この PGI の記事に含まれています。
記事の要約からの引用: 「OpenACC カーネルと並列構造はそれぞれ、同じ問題を解決しようとし、ループの並列処理を識別して、それを機械の並列処理にマッピングします。カーネルの構造はより暗黙的であり、コンパイラが並列処理を見つけてマッピングする自由度を高めます。ターゲット アクセラレータの要件に対応します。並列構造はより明示的であり、いつ合法的で適切かを判断するには、プログラマによるより多くの分析が必要です。」
OpenACCディレクティブとGPUカーネルは、同じものを表す2つの方法、つまり並列で実行できるコードのセクションです。
OpenACCは、GPUを利用するために既存のアプリを改良する場合や、メモリ管理などの問題に関連する詳細をコンパイラに処理させることが望ましい場合に最適です。これにより、アプリの作成が速くなり、パフォーマンスが低下する可能性があります。
カーネルは、GPUアプリを最初から作成する場合や、よりきめ細かい制御が必要な場合に最適です。これにより、アプリの作成に時間がかかる可能性がありますが、パフォーマンスが向上する可能性があります。
GPUを初めて使用する人は、OpenACCの方が見慣れているため、OpenACCを使いたくなるかもしれません。しかし、実際には、逆の方法でカーネルの作成から始めてから、OpenACCに移行して、一部のプロジェクトの時間を節約する方がよいと思います。その理由は、OpenACCはリークの多い抽象化であるためです。そのため、OpenACCはGPUの詳細が抽象化されているように見えるかもしれませんが、それでもそこにあります。そのため、バックグラウンドで何が起こっているのかを理解せずにOpenACCを使用してGPUコードを記述すると、コンパイルしようとすると奇妙なエラーメッセージが表示され、アプリのパフォーマンスが低下するため、イライラする可能性があります。
並列構造
アクセラレータ デバイスでの並列実行用にコンパイルする必要があるプログラムの領域を定義します。
並列ループ ディレクティブは、影響を受けるループを並列化することが安全で望ましいというプログラマによる主張です。これは、プログラマーがコード内の並列処理を正しく識別し、並列化が安全でないコード内のものをすべて削除することに依存しています。ループが並列化される可能性があるとプログラマが誤って断言した場合、結果として得られるアプリケーションは誤った結果を生成する可能性があります。
並列構造により、コンパイラーがアクセラレーターでの作業を構造化する方法をより細かく制御できます。したがって、コードを自動的に並列化するコンパイラの機能に大きく依存することはありません。
同じデータにアクセスする 2 つの後続のループで並列ループが使用される場合、コンパイラは 2 つのループ間でホストとデバイスの間でデータをコピーする場合とコピーしない場合があります。
コード内の並列ループを既に認識している可能性がある経験豊富な並列プログラマーは、並列ループのアプローチがより望ましいと考えるでしょう。
例参照
#pragma acc parallel
{
#pragma acc loop
for (i=0; i<n; i++)
a[i] = 3.0f*(float)(i+1);
#pragma acc loop
for (i=0; i<n; i++)
b[i] = 2.0f*a[i];
}
カーネルを 1 つ生成する
2 つのループの間に障壁はありません。最初のループが終了する前に 2 番目のループが開始される場合があります。(これは OpenMP とは異なります)。
カーネル コンストラクト
アクセラレータ デバイスで実行するために一連のカーネルにコンパイルする必要があるプログラムの領域を定義します。
カーネル構造について注意すべき重要な点は、コンパイラがコードを分析し、安全であることが確実な場合にのみ並列化するということです。場合によっては、コンパイル時にループが安全に並列化されているかどうかを判断するのに十分な情報がコンパイラーにないことがあります。
kernels コンストラクトは、コンパイラがターゲット アクセラレータに適していると判断した方法でコードを並列化および最適化するための最大の余裕をコンパイラに与えますが、コードを自動的に並列化するコンパイラの機能にも大きく依存します。
カーネル コンストラクトが提供するもう 1 つの注目すべき利点は、複数のループが同じデータにアクセスする場合、アクセラレータに 1 回だけコピーされるため、データ モーションが少なくなる可能性があることです。
並列プログラミングの経験が少ないプログラマーや、コードに分析が必要な多数のループが含まれているプログラマーは、コンパイラーに負担がかかるため、カーネルのアプローチがはるかに簡単であることに気付くかもしれません。
例参照
#pragma acc kernels
{
for (i=0; i<n; i++)
a[i] = 3.0f*(float)(i+1);
for (i=0; i<n; i++)
b[i] = 2.0f*a[i];
}
2 つのカーネルを生成する
2 つのループの間に暗黙の障壁があります。最初のループが終了した後に 2 番目のループが開始されます。