インテル® C++ コンパイラー 18.0 デベロッパー・ガイドおよびリファレンス

並列処理モデル

OpenMP* API コンパイラー・プラグマを持つプログラムは、単一のスレッドとして実行を開始します。これは、初期スレッドの実行と呼ばれます。最初の並列構造が検出されるまで、初期スレッドは、シーケンシャルに実行します。

OpenMP* API では、#pragma omp parallelプラグマは並列構造の範囲を定義します。初期スレッドが並列構造を検出すると、その初期スレッドがチームのマスターとなり、スレッドのチームを作成します。並列構造に囲まれたプログラム文はすべて (そこから呼び出されるすべてのルーチンを含む)、チーム内の各スレッドごとに並列で実行されます。

構造内で字句的に囲まれた文は、構造の静的範囲を定義します。動的範囲には、スレッドによる構造の実行中に実行されるすべての文 (呼び出される全ルーチンを含む) が含まれます。

1 つのスレッドが並列構造によって囲まれた構造化ブロックの終わりに到達すると、そのスレッドは、チーム内のすべてのスレッドが到着するまで待機します。すべてのスレッドが到着すると、チームは消滅し、マスタースレッドのみが次の並列構造のコードの実行を続けます。チーム内の他のスレッドは、別のチームの形成で必要になるまで待機状態になります。単一プログラム内で並列構造は何回でも指定できます。結果として、プログラム実行中に、スレッドのチームは何度も生成され消滅します。

次の例は、上位レベルから OpenMP* 構造の実行モデルを示します。コードのコメントは、各構造やセクションの説明です。


main() {                     // シリアル実行の開始。
  ...                        // 初期スレッドのみ実行する。
  #pragma omp parallel {     // 並列構造を開始し、チームを形成する。
    #pragma omp sections {   // ワークシェア構造の開始。
       #pragma omp section   // 1 つの作業単位。
       {...}
       #pragma omp section   // 別の作業単位。
       {...}
    }                        // 両方の作業単位の完了を待機する。
    ...                      // このコードは各チームメンバーにより実行される。
  #pragma omp for nowait     // ワークシェア構造の開始。
    for(...){               // 各反復のチャンクが作業単位。
      ...                    // チーム内で作業を分配。
    }                        // ワークシェア構造の終了。
                             // nowait が指定されているので先に進む。
  #pragma omp critical       // クリティカル・セクションの開始。
    {...}                    // 一度に 1 つのスレッドでのみ実行する。
  ...                        // このコードは各チームメンバーにより実行される。
  #pragma omp barrier        // すべてのチームメンバーが到着するまで待機する。
  ...                        // このコードは各チームメンバーにより実行される。
  }                          // 並列構造の終了。
                             // チームを解散し、シリアル実行を継続する。
...                          // 別の並列構造。
}                            // シリアル実行の終了。

親なしプラグマの使用

並列構造内で呼び出されたルーチンで、プラグマを使用することができます。並列構造の静的範囲ではなく、動的範囲のプラグマは、親なしプラグマと呼ばれます。親なしプラグマは、プログラムのシーケンシャル・バージョンに最小限の変更を行うだけで、プログラム部分を並列に実行できます。この機能を使用すると、プログラム・コール・ツリーの最上位レベルで並列構造をコーディングでき、ディレクティブを使用して呼び出されるすべてのルーチンの実行を制御することができます。次に例を示します。

int main(void) {
  #pragma omp parallel {
     phase1();
  } 
} 

void phase1(void) {
  #pragma omp for // 親なしプラグマ
  for(i=0; i < n; i++) { some_work(i); } 
}

並列領域が routine phase 1 で字句的に指定されていないので、これが親なし omp for ループプラグマとなります。

データ環境制御

並列およびワークシェア構造内でデータ環境を制御できます。ディレクティブとデータ環境節を使用して、threadprivate プラグマで名前付きグローバル・ライフタイム・オブジェクトをプライベート化したり、ディレクティブがサポートする場合はデータ環境節でデータスコープ属性を制御できます。

データスコープ属性節:

データコピー節:

複数のプラグマを使用して、変数のデータスコープ属性を指定した構造の継続期間中にその属性を制御することができます。ただし、プラグマでデータスコープ属性節を指定しない場合、ディレクティブに影響を受ける変数の動作はデフォルトのスコープ規則によって決まります。これは、OpenMP* API 仕様で説明されています。

使用するスレッド数の決定

ワークロードがアプリケーションの (一定ではない) 入力に依存するアプリケーションでは、入力サイズが確認されるランタイム時まで、使用するスレッド数の決定を保留します。スレッド数に影響を与えるワークロードの入力引数には、行列のサイズ、データベースのサイズ、イメージ/ビデオのサイズおよび解像度、ツリーベースの構造体の階層の深さ/幅/種類、およびリストベースの構造体のサイズなどが含まれます。同様に、プロセッサー数が一定ではないシステムで実行するように設計されたアプリケーションでも、マシンサイズが確認されるアプリケーション・ランタイム時まで、使用するスレッド数の決定を保留します。

入力データから処理の量を予測できないアプリケーションでは、適切なスレッド数を選択するために、測定ステップを取り入れて、ワークロードおよびシステムの特性を確認することを推奨します。測定結果は、ファイルシステムなど永久保管場所に格納することによって、(測定ステップのオーバーヘッドを抑え) 永続的に使用することができます。

システムの処理ユニット数を超えるスレッドを同時に使用しないでください。同時に使用すると、オペレーティング・システムによりプロセッサー上でスレッドが多重化され、パフォーマンスが最適化されません。

ライブラリーを開発する場合 (アプリケーション全体を開発する場合とは異なり)、ライブラリーによって使用されるスレッド数を簡単に選択できるメカニズムを使用してください。これは、より高度な並列化を使用している場合、ライブラリー内の並列化が必要ないためです。

並列領域で num_threads 節を使用して、使用するスレッド数を制御し、並列領域で if 節を使用してマルチスレッドを使用するかどうかを決定します。omp_set_num_threads ルーチンも使用することができますが、呼び出し側のスレッドにより作成される並列領域にも影響します。num_threads 節の効果はローカルであるため、ほかの並列領域には影響しません。明示的なスレッド数の設定には、次のような短所があります。

  1. 多数のプロセッサーを搭載したシステムで、アプリケーションはすべてのプロセッサーではなく、その一部を使用します。

  2. 少数のプロセッサーを搭載したシステムで、アプリケーションはパフォーマンスの低下につながるオーバーサブスクリプションを強制する可能性があります。

インテルの OpenMP* ランタイムは、omp_set_num_threads ルーチンを使用しない限り、利用可能な論理プロセッサー数と同じ数のスレッドを生成します。実際の制限を特定するには、omp_get_thread_limit()omp_get_max_active_levels() を使用します。システムがオーバーロードしないように、開発者はスレッドの使用と並列処理の入れ子構造に注意する必要があります。OMP_THREAD_LIMIT 環境変数は、OpenMP* プログラム全体に使用する OpenMP* スレッドの数を制限します。OMP_MAX_ACTIVE_LEVELS 環境変数は、アクティブな入れ子構造の並列領域の数を制限します。

バインド設定

OpenMP* 構文を別の OpenMP* 構文で入れ子にしたり、その動作については、さまざまなバインド設定で指定されます。

OpenMP* 構文のバインド領域は、実行コンテキストとディレクティブの有効範囲を決定する領域です。

OpenMP* 構文のバインドタスクは、領域の実行により影響を受ける、または領域の実行にコンテキストを与えるタスクのセットです。特定の構文のバインドタスクは、すべてのタスク、現在のチームタスク、または生成されるタスクです。

OpenMP* 構文のバインドスレッドは、領域の実行により影響を受ける、または領域の実行にコンテキストを与えるスレッドのセットです。特定の構文のバインドスレッドは、デバイス上のすべてのスレッド、グループのすべてのスレッド、現在のチーム、または到達スレッドです。