このセクションでは、OpenMP アプリケーションのパフォーマンスを向上させる一般的な技法について説明します。
同期を最小限に抑える。
barrier、critical、ordered、taskwait、ロックなどの同期の使用を避けるか、最小限にします。
可能な場合は nowait 節を使用して、冗長または不要なバリアを取り除いてください。たとえば、並列領域の最後につねに暗黙のバリアがあります。領域内にある後続のコードがないワークシェアリングループに nowait を追加すると、1 つの冗長なバリアが取り除かれます。
プログラム内のすべての critical セクションによってデフォルトの同じロックが使用されないように、必要に応じて名前付きの critical セクションを使用してきめ細かくロックします。
OMP_WAIT_POLICY、SUNW_MP_THR_IDLE、または SUNW_MP_WAIT_POLICY 環境変数を使用して、待機スレッドの動作を制御します。デフォルトでは、アイドル状態のスレッドがある時間経過後にスリープします。タイムアウト期間の終わりまでに作業が見つからない場合、スレッドはスリープ状態になり、ほかのスレッドを犠牲にしてプロセッササイクルを浪費することを回避します。デフォルトのタイムアウト期間がアプリケーションに対して適切ではない場合、スレッドがスリープするのが早すぎたり、遅すぎたりすることがあります。通常、アプリケーションが専用のプロセッサで実行される場合は、待機スレッドをスピンさせるアクティブ待機ポリシーを使用すると、パフォーマンスが向上します。アプリケーションがほかのアプリケーションと同時に実行される場合は、待機スレッドをスリープさせるパッシブ待機ポリシーを使用すると、システムスループットが向上します。
できるかぎりもっとも高いレベル (いちばん外側のループなど) で並列化してください。1 つの並列領域で複数のループを囲みます。一般に、並列化のオーバーヘッドを抑制するには、並列領域をできるかぎり大きくします。たとえば、この構文では効率が低くなります。
#pragma omp parallel { #pragma omp for { ... } } #pragma omp parallel { #pragma omp for { ... } }
次の構文の方が効率が高くなります。
#pragma omp parallel { #pragma omp for { ... } #pragma omp for { ... } }
parallel 構文の内部で入れ子になっているワークシェアリング for/do 構文の代わりに、parallel for/do 構文を使用します。たとえば、この構文では効率が低くなります。
#pragma omp parallel { #pragma omp for { ... statements ... } }
この構文の方が効率が高くなります。
#pragma omp parallel for { ... statements ... }
可能な場合は、並列ループをマージして、並列処理のオーバーヘッドを減らします。たとえば、2 つの parallel for ループをマージします。
#pragma omp parallel for for (i=1; i<N; i++) { ... statements 1 ... } #pragma omp parallel for for (i=1; i<N; i++) { ... statements 2 ... }
マージされた単一の parallel for ループの方が効率的です。
#pragma omp parallel for for (i=1; i<N; i++) { ... statements 1 ... ... statements 2 ... }
the OMP_PROC_BIND または SUNW_MP_PROCBIND 環境変数を使用して、スレッドをプロセッサにバインドします。static スケジュール指定ととともにプロセッサバインディングを使用すると、並列領域の前回呼び出し以降、その領域内のスレッドがアクセスするデータがローカルキャッシュに存在する、特定のデータ再利用パターンを持つアプリケーションにメリットがあります。Chapter 5, プロセッサバインディング (スレッドアフィニティー)を参照してください。
可能な場所では、できるかぎり single ではなく、master を使用してください。
master ディレクティブは、暗黙バリアのない if 文として実装されます。if (omp_get_thread_num() == 0) {...}
single 構文は、ほかのワークシェアリング構文に似た実装になります。どのスレッドが最初に single に達するかを記録すると、実行時のオーバーヘッドが増加します。さらに、nowait が指定されていない場合は暗黙バリアがあり、効率が低くなります。
適切なループスケジュールを選択してください。
static ループスケジュールでは同期が要求されず、データがキャッシュに収まる場合、データの近傍性を維持できます。ただし、static スケジュールは、負荷の不均衡をもたらすことがあります。
dynamic および guided ループスケジュールは、どのチャンクが割り当てられたかを記録するため、同期オーバーヘッドを招きます。そのスケジュールによってデータのローカル性の低下をもたらすことがあります。ただし、負荷均衡が改善することがあります。チャンクのサイズを変えて試してください。
効率的でスレッドセーフなメモリー管理を使用します。アプリケーションが malloc() 関数および free() 関数を明示的に、あるいは暗黙的に動的配列、割り当て可能な配列、ベクトル化された組み込み関数などのコンパイラ生成のコード内で使用していることがあります。標準 C ライブラリ libc.so にあるスレッドセーフな malloc() および free() には、内部ブロックを原因とする大きな同期オーバーヘッドがあります。libmtmalloc.so ライブラリなどのほかのライブラリでは、より高速のバージョンが提供されています。–lmtmalloc を指定して、libmtmalloc.so とリンクします。
小さいデータセットの場合、OpenMP の並列領域が十分に機能しないことがあります。parallel 構文では if 節を使用して、ある程度のパフォーマンス向上が期待できる場合にのみ領域を並列実行させるように指定します。
アプリケーションにある程度以上のスケーラビリティーがない場合は、入れ子並列処理を試してください。ただし、すべての入れ子並列領域のスレッドチームはバリアで同期する必要があり、同期のオーバーヘッドが発生するため、入れ子並列処理は注意して使用してください。また、入れ子並列処理によってマシンに過剰な要求が発生し、パフォーマンスが低下することがあります。
オーバーヘッドが大きくなる可能性があるため、lastprivate の使用には注意してください。
領域から戻る前に、スレッドのプライベートメモリーから共有メモリーにデータをコピーする必要があります。
lastprivate のチェックが追加されます。たとえば、lastprivate 節を指定したワークシェアリングループのコンパイルされたコードは、順序の最後の繰り返しを実行するスレッドをチェックします。これにより、ループ内の各チャンクの終わりに追加の処理が生じることになり、多数のチャンクがある場合はその負荷が大きくなります。
明示的な flush の使用には注意してください。flush によってデータがメモリーに格納され、以降のデータアクセスで、メモリーからのリロードが必要になることがあります。このすべてが効率の低下をもたらします。