ここでは、OpenMP ライブラリー関数と環境変数の使用方法について説明し、OpenMP を使用してパフォーマンスを向上するためのいくつかのガイドラインを示します。
OpenMP は、特定の関数呼び出しおよび環境変数を提供します。ここで使用する主要な関数および環境変数については、次のトピックを参照してください。
関数呼び出しを使用するには、omp_lib.h ヘッダーファイルをインクルードするかまたは use omp_lib を指定してモジュールファイルを使用し (コンパイラーのインストール時に、INCLUDE ディレクトリーにインストールされます)、-openmp (Linux* および Mac OS* X) オプションまたは /Qopenmp (Windows*) オプションを使用してアプリケーションをコンパイルします。
次の例では、OpenMP 関数を使用してアルファベットを出力する方法といくつかの重要な概念について説明します。
最初に、宣言子の代わりに関数を使用するには、コードを書き換える必要があります。コードの書き換えには、追加のデバッグ、テスト、メンテナンスが伴います。
また、OpenMP サポートなしにコンパイルすることは困難です。
次のループではスレッド数が 26 の倍数でない場合、アルファベットの文字はすべて出力されません。このように、単純なバグは簡単に引き起こされます。
最後に、ワークキューのアルゴリズムを独自に作成しない限り、ループ・スケジューリングの調整ができなくなります。ワークキューのアルゴリズムを独自に作成する場合、一般的には例に示すような STATIC スケジューリングが多く、自らのスケジューリングによって制限されることになります。
例 |
---|
include "omp_lib.h" integer i integer LettersPerThread, ThisThreadNum, StartLetter, EndLetter call omp_set_num_threads(4) !$OMP PARALLEL PRIVATE(i) ! OMP_NUM_THREADS is not a multiple of 26, ! which can be considered a bug in this code. LettersPerThread = 26 / omp_get_num_threads() ThisThreadNum = omp_get_thread_num() StartLetter = 'a'+ThisThreadNum*LettersPerThread EndLetter = 'a'+ThisThreadNum*LettersPerThread+LettersPerThread DO i = StartLetter, EndLetter - 1 write( *,FMT='(A)',ADVANCE='NO') char(i) END DO !$OMP END PARALLEL write(*,*) end |
スレッド・アプリケーションのデバッグには細心の注意が必要です。これは、デバッガーによってランタイム時のパフォーマンスが左右され、競合状態が表面化しないことがあるためです。PRINT 文でさえも、問題を発見しにくくすることがあります。これは、PRINT 文が、同期およびオペレーティング・システム関数を使用するためです。OpenMP 自体も、プライベート変数と共有変数を区別するために追加の構造を挿入するため、さらに問題を複雑にします。インテル® デバッガーなど、OpenMP をサポートする専用のデバッガーを使用することにより、変数を検証しステップ実行することが可能になります。また、インテル® スレッド・チェッカーを使用して、発見が困難なスレッド化エラーを分析して検出することができます。高度なデバッグツールを使用しなくても、排除処理が問題の特定に役立つこともあります。
誤りの多くは競合状態です。ほとんどの競合状態は、本来ならばプライベート変数として宣言されるべき共有変数によって引き起こされます。最初に、並列領域内の変数から検証し、必要に応じて変数がプライベートとして宣言されていることを確認します。次に、並列領域内の関数呼び出しを確認します。
次に示す DEFAULT(NONE) 節は、見つけるのが困難な変数を探すのに役立ちます。DEFAULT(NONE) を指定する場合、各変数はデータ共有属性節とともに宣言する必要があります。
例 |
---|
!$OMP PARALLEL DO DEFAULT(NONE) PRIVATE(x,y) SHARED(a,b) |
その他のよくある誤りは、初期化されていない変数の使用です。プライベート変数は、並列構造の入口では初期値を持っていません。FIRSTPRIVATE 節および LASTPRIVATE 節を使用して初期化してください (これには余分なオーバーヘッドが伴うため、必要な場合のみ実行します)。
ここまで試してもバグが見つからない場合は、スコープの縮小について考慮します。バイナリーハントを試してください。また別の方法として、並列領域の大きなチャンクをクリティカル・セクションと見なします。バグが含まれている疑いのあるコード領域を選択して、クリティカル・セクションに配置します。クリティカル・セクション内では動作し、クリティカル・セクション外では失敗するコードのセクションを探します。そして、変数を調べて、バグが明白であるかどうかを検証します。それでも動作しない場合は、コンパイラー固有の環境変数 KMP_LIBRARY = serial を設定して、プログラム全体をシリアルで実行します。
この時点でコードがまだ動作しない場合は、-openmp (Linux および Mac OS X) または /Qopenmp (Windows) オプションを指定しないでコンパイルして、シリアルバージョンが動作することを確認してください。
OpenMP スレッド・アプリケーションのパフォーマンスは、次の要因に大きく依存します。
基本となるシングルスレッド・コードのパフォーマンス。
CPU 稼働率、アイドルスレッド、負荷のバランス。
複数のスレッドにより並列実行されるアプリケーションの比率。
スレッド間における同期と通信の量。
スレッドを作成、管理、破棄、および同期するのに必要なオーバーヘッド (fork-join と呼ばれるシングルから並列への切り替え (single-to-parallel)、または並列からシングルへの切り替え (parallel-to-single) によって増加します)。
メモリー、バスの帯域幅、CPU 実行ユニットなど、共有リソースのパフォーマンス制約。
共有メモリーまたは偽の共有メモリーによって生じるメモリーの競合。
パフォーマンスの解析は常に、適切に構成された並列化アルゴリズムまたはアプリケーションから始めます。例えば、バブルソートの並列化は、手動で最適化されたアセンブリー言語であっても、良い開始位置とはいえません。スケーラビリティーに注意してください。2 個の CPU で実行するプログラムの作成は、n 個の CPU で実行するプログラムの作成よりも効率的ではありません。OpenMP では、スレッド数はコンパイラーによって選択されます。このため、スレッド数に関係なく動作するプログラムが非常に望ましいといえます。生産/消費構造は、2 つのスレッド用に作成されているため、効率的ではありません。
アルゴリズムが決定したら、対象のインテル® アーキテクチャーでコード (シングルスレッド・バージョンが望ましい) が効率的に実行されることを確認します。-openmp (Linux および Mac OS X) または /Qopenmp (Windows) オプションをオフにするか、あるいは -openmp-stubs (Linux および Mac OS X) または /Qopenmp-stubs (Windows) でビルドして、シングルスレッド・バージョンを生成し、通常の最適化を通して実行します。
シングルスレッドのパフォーマンスを確認したら、マルチスレッド・バージョンを生成して、解析を始めます。
最適化を行うには、忍耐力、経験、実践が必要です。最適化するアプリケーションと同じようにコンピューターのリソースを使用する小規模なテストプログラムを作成して、何をすると速くなるのか試してみてください。コードの並列セクションで異なる scheduling 節を試すことも忘れないでください。