インテル® C++ コンパイラー 19.0 デベロッパー・ガイドおよびリファレンス
ここでは、OpenMP* ライブラリー関数と環境変数の使用方法について説明し、OpenMP* を使用してパフォーマンスを向上するためのいくつかのガイドラインを示します。
OpenMP* は、特定の関数呼び出しおよび環境変数を提供します。ここで使用する主要な関数および環境変数については、次のトピックを参照してください。
この関数呼び出しを使用するには、omp.h ヘッダーファイルをインクルードします。このファイルは、コンパイラーのインストール時に INCLUDE ディレクトリーにインストールされ、[Q]openmp オプションを使用してアプリケーションをコンパイルします。
次の例では、OpenMP* 関数を使用してアルファベットを出力する方法といくつかの重要な概念について説明します。
例 |
---|
|
スレッド・アプリケーションのデバッグには細心の注意が必要です。これは、デバッガーによってランタイム時のパフォーマンスが左右され、競合状態が表面化しないことがあるためです。print 文でさえも、問題を発見しにくくすることがあります。これは、PRINT 文が、同期およびオペレーティング・システム関数を使用するためです。OpenMP* 自体も、プライベート変数と共有変数を区別するために追加の構造を挿入するため、さらに問題を複雑にします。OpenMP* をサポートするデバッガーを使用することにより、変数を検証しステップ実行することが可能になります。また、インテル® Inspector を使用して、発見が困難なスレッド化エラーを分析して検出することができます。高度なデバッグツールを使用しなくても、排除処理が問題の特定に役立つこともあります。
誤りの多くは競合状態です。ほとんどの競合状態は、本来ならばプライベート変数として宣言されるべき共有変数によって引き起こされます。最初に、並列領域内の変数から検証し、必要に応じて変数がプライベートとして宣言されていることを確認します。次に、並列領域内の関数呼び出しを確認します。デフォルトでは、スタックで宣言される変数はプライベートですが、C/C++ では、static キーワードによって変数はグローバルヒープに配置されるため、OpenMP* ループで共有されます。
次に示す default(none) 節は、見つけるのが困難な変数を探すのに役立ちます。default(none) を指定する場合、各変数はデータ共有属性節とともに宣言する必要があります。
例 |
---|
|
その他のよくある誤りは、初期化されていない変数の使用です。プライベート変数は、並列構造の入口では初期値を持っていません。firstprivate 節および lastprivate 節を使用して初期化してください (これには余分なオーバーヘッドが伴うため、必要な場合のみ実行します)。
ここまで試してもバグが見つからない場合は、スコープの縮小について考慮します。バイナリーハントを試してください。並列構造で if(0) を使用して並列セクションを再度シリアルにするか、プラグマをコメントアウトします。また別の方法として、並列領域の大きなチャンクをクリティカル・セクションと見なします。バグが含まれている疑いのあるコード領域を選択して、クリティカル・セクションに配置します。クリティカル・セクション内では動作し、クリティカル・セクション外では失敗するコードのセクションを探します。そして、変数を調べて、バグが明白であるかどうかを検証します。それでも動作しない場合は、コンパイラー固有の環境変数 KMP_LIBRARY=serial を設定して、プログラム全体をシリアルで実行します。
この時点でコードがまだ動作せず、OpenMP* API 関数呼び出しを使用していない場合は、[Q]openmp オプションを指定しないでコンパイルして、シリアルバージョンが動作することを確認してください。OpenMP* API 関数呼び出しを使用している場合は、[Q]openmp-stubs オプションを使用してください。
OpenMP* スレッド・アプリケーションのパフォーマンスは、次の要因に大きく依存します。
基本となるシングルスレッド・コードのパフォーマンス。
CPU 稼働率、アイドルスレッド、負荷のバランス。
複数のスレッドにより並列実行されるアプリケーションの比率。
スレッド間における同期と通信の量。
スレッドを生成、管理、破棄、および同期するのに必要なオーバーヘッド (fork-join と呼ばれるシングルから並列への切り替え (single-to-parallel)、または並列からシングルへの切り替え (parallel-to-single) によって増加します)。
メモリー、バスの帯域幅、CPU 実行ユニットなど、共有リソースのパフォーマンス制約。
共有メモリーまたは偽の共有メモリーによって生じるメモリーの競合。
パフォーマンスの解析は常に、適切に構成された並列化アルゴリズムまたはアプリケーションから始めます。例えば、バブルソートの並列化は、手動で最適化されたアセンブリー言語であっても、良い開始位置とはいえません。スケーラビリティーに注意してください。2 個の CPU で実行するプログラムの作成は、n 個の CPU で実行するプログラムの作成よりも効率的ではありません。OpenMP* では、スレッド数はコンパイラーによって選択されます。このため、スレッド数に関係なく動作するプログラムが非常に望ましいといえます。生産/消費構造は、2 つのスレッド用に作成されているため、効率的ではありません。
アルゴリズムが決定したら、対象のインテル® アーキテクチャーでコード (シングルスレッド・バージョンが望ましい) が効率的に実行されることを確認します。[Q]openmp オプションをオフにするか、あるいは [Q]openmp-stubs オプションでビルドして、シングルスレッド・バージョンを生成し、通常の最適化を通して実行します。
シングルスレッドのパフォーマンスを確認したら、マルチスレッド・バージョンを生成して、解析を始めます。
最適化を行うには、忍耐力、経験、実践が必要です。最適化するアプリケーションと同じようにコンピューターのリソースを使用する小規模なテストプログラムを作成して、何をすると速くなるのか試してみてください。コードの並列セクションで異なる scheduling 節を試すことも忘れないでください。並列領域のオーバーヘッドが実行時間に対して大きい場合、if 節を使用してセクションをシリアルに実行すると良いかもしれません。