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

自動並列化

インテル® C++ コンパイラーの自動並列化機能は、入力プログラムのシリアル部分を同等のマルチスレッド・コードに自動的に変換します。自動並列化機能は、ワークシェア候補のループを特定し、正しい並列実行を確認するためにデータフロー解析を行います。また、OpenMP* ディレクティブのプログラミングに必要な場合には、スレッドコード生成のデータをパーティショニングします。OpenMP* と自動並列化機能では、マルチプロセッサー・システム、デュアルコア・プロセッサー・システム上の共有メモリーによるパフォーマンス・ゲインも実現します。

自動並列化は、アプリケーション・ソース・コード中のループのデータフローを解析して、安全かつ効率的に並列実行可能なループに対するマルチスレッド・コードを生成します。

これにより、対称型マルチプロセッサー (SMP) システムの並列アーキテクチャーを活用できます。

インテル® C++ コンパイラーのガイド付き自動並列化機能は、並列化を行える可能性のあるシリアルコードの部分を見つけるのに役立ちます。[Q]guide コンパイラー・オプションを使用して、並列化、ベクトル化、データ変換に関するアドバイスを得られます。

自動並列化は、次のような開発者の負担を軽減します。

OpenMP* ディレクティブはシリアル・アプリケーションを素早く並列アプリケーションに変換できますが、開発者は、並列処理を含み、適切なコンパイラー・ディレクティブを追加するアプリケーション・コードの特定部分を明示的に識別する必要があります。[Q]parallel オプションで起動された自動並列化は、並列処理を含むループ構造を自動的に識別します。コンパイル中、コンパイラーは、並列処理のためにコードシーケンスを別々のスレッドに自動的に分割しようと試みます。ほかに開発者にかかる負荷はありません。

Linux* または macOS* システムで、自動並列化を使用するプログラムを実行するには、プログラムをコンパイルおよびリンクする際に、-parallel コンパイラー・オプションを含める必要があります。

このオプションを使用すると、互換マイクロプロセッサーおよびインテル製マイクロプロセッサーの両方で並列化が有効になります。実行ファイルは、互換マイクロプロセッサーよりもインテル製マイクロプロセッサーにおいてより優れたパフォーマンスが得られる可能性があります。また、並列化は、/arch (Windows*)、-m (Linux* および macOS*)、[Q]x などの特定のオプションによる影響を受けます。

シリアルコードは分割できるので、コードを複数のスレッドで同時に実行することができます。例えば、次のようなシリアルコードの例を考えてみます。

例 1: オリジナルのシリアルコード

void ser(int *a, int *b, int *c) {
  for (int i=0; i<100; i++)
    a[i] = a[i] + b[i] * c[i]; 
}

次の例は、2 つのスレッドで同時に実行できるように、前の例で示したループの反復空間を分割する方法を示しています。

例 2: 変換された並列コード

void par(int *a, int *b, int *c) {
  int i;
  // スレッド 1
  for (i=0; i<50; i++)
    a[i] = a[i] + b[i] * c[i];
  // スレッド 2
  for (i=50; i<100; i++)
    a[i] = a[i] + b[i] * c[i]; 
}

自動ベクトル化と並列化

ベクトル化の自動処理機能は、並列で実行できるプログラム内の演算を検出し、シーケンシャル・プログラムをデータ型に応じて、2、4、8、または 16 までの要素を 1 つの演算で処理するように変換します。場合によっては、自動並列化とベクトル化を組み合わせて最良のパフォーマンスを得ることができます。

次の例では、並列化とベクトル化による利点を明示的に得るためのコードの記述方法を示します。[Q]parallel オプションを使用して下記のコードをコンパイルすると、コンパイラーは外側のループを並列化して、最内ループをベクトル化します。

#include <stdio.h> 
#define ARR_SIZE 500 // 配列を定義
int main() {
  int matrix[ARR_SIZE][ARR_SIZE];
  int arrA[ARR_SIZE]={10};
  int arrB[ARR_SIZE]={30};
  int i, j;
  for(i=0;i<ARR_SIZE;i++) {
     for(j=0;j<ARR_SIZE;j++) { matrix[i][j] = arrB[i]*(arrA[i]%2+10); }
  } printf("%d\n",matrix[0][0]); 
}

正しいオプションを使用して上記の例をコンパイルすると、コンパイラーは次のような結果を表示します。

vectorization.c(18) : (列 6) リマーク: ループがベクトル化されました。
	 vectorization.c(16) : (列 3) リマーク: ループが自動並列化されました。

OpenMP* ディレクティブを各自のコードに追加するだけの簡単な処理で、開発者はシーケンシャル・プログラムを並列プログラムに変換できます。OpenMP* ディレクティブを有効にするには、[Q]openmp オプションを指定する必要があります。

次の例では、コード内で OpenMP* プラグマを使用する 1 つの方法を示します。

#include <stdio.h> 
#define ARR_SIZE 100 // 配列を定義
void foo(int ma[][ARR_SIZE], int mb[][ARR_SIZE], int *a, int *b, int *c); 
int main() {
  int arr_a[ARR_SIZE];
  int arr_b[ARR_SIZE];
  int arr_c[ARR_SIZE];
  int i,j;
  int matrix_a[ARR_SIZE][ARR_SIZE];
  int matrix_b[ARR_SIZE][ARR_SIZE];
  #pragma omp parallel for 
// 配列と行列を初期化
  for(i=0;i<ARR_SIZE; i++) {
    arr_a[i]= i;
    arr_b[i]= i;
    arr_c[i]= ARR_SIZE-i;
    for(j=0; j<ARR_SIZE;j++) {
       matrix_a[i][j]= j;
       matrix_b[i][j]= i;
    }
  }
  foo(matrix_a, matrix_b, arr_a, arr_b, arr_c); 
} 
void foo(int ma[][ARR_SIZE], int mb[][ARR_SIZE], int *a, int *b, int *c) {                                  
  int i, num, arr_x[ARR_SIZE];
  #pragma omp parallel for private(num) 
// OpenMP* プラグマ parallel for を使用して並列処理を表現し、
// マルチスレッド・コードを生成するようにコンパイラーに指示。
// 配列 arr_X、mb、b、および c は OpenMP* のデータ共有規則に
// 基づきスレッド間で共有される。スカラー num は各スレッドで
// プライベートとして指定される。
  for(i=0;i<ARR_SIZE;i++) {
     num = ma[b[i]][c[i]];
     arr_x[i]= mb[a[i]][num];
     printf("Values: %d\n", arr_x[i]); // 値 0-ARR_SIZE-1 を出力
   } 
}

OpenMP* を使用するオプションは、インテル製マイクロプロセッサーおよび互換マイクロプロセッサーの両方で利用可能ですが、両者では結果が異なります。両者の結果が異なる可能性のある OpenMP* 構造および機能の主なリストは次のとおりです: ロック (内部的なものおよびユーザーが利用可能なもの)、SINGLE 構造、バリア (暗黙的および明示的)、並列ループ・スケジュール、リダクション、メモリーの割り当て、スレッド・アフィニティー、バインド。

並列化レポートの使用

並列化レポートを生成するには、-qopt-report-phase=par (Linux* および macOS*) または /Qopt-report-phase:par (Windows*) と -qopt-report=n (Linux* および macOS*) または /Qopt-report:n (Windows*) を一緒に指定します。デフォルトでは、中レベルの詳細 (n=2) が含まれる自動並列化レポートを生成します。異なる詳細レベルのレポートを生成するには、[Q]opt-report オプションと [Q]opt-report-phase の引数を変更します。n=5 を指定すると、最大限の情報を含むレポートが生成されます。

次のようなコマンドを入力して、レポートを生成します。

オペレーティング・システム コマンド

Linux*

icpc -c -parallel -qopt-report-phase=par -qopt-report:5 sample.cpp

macOS*

icl++ -c -parallel -qopt-report-phase=par -qopt-report:5 sample.cpp

Windows*

icl /c /Qparallel /Qopt-report-phase:par /Qopt-report:5 sample.cpp

-c (Linux* および macOS*) または /c (Windows*) を指定すると、リンクは行われず、オブジェクト・ファイル生成後にコンパイルが停止されます。上記のコマンド例は、実行ファイルを生成せずにコンパイルします。

レポートは、デフォルトでオブジェクト・ファイルと同じディレクトリーに出力され、ファイル名はオブジェクト・ファイルと同じで拡張子は .optrpt になります。上記のコマンドライン例では、sample.optrpt という名前の出力ファイルが生成されます。異なる出力ファイル名を指定するには、[Q]opt-report-file を使用します。stdout または stderr に出力するには、引数 stdout または stderr を指定します。

次のサンプルコードを使って、完全な診断レポートを生成してみましょう。

void no_par(void) {
  int i;
  int a[1000];
  for (i=1; i<1000; i++) {
    a[i] = (i * 2) % i * 1 + sqrt(i);
    a[i] = a[i-1] + i;
  }
} 

上記のコードに対し、コンパイラーは次のような診断レポートを出力します。ほとんどの場合、この情報から処理について知ることができます。

並列化レポートの例
procedure: no_par
sample.c(13):(3) リマーク #15048: 分配されたループが自動並列化されました。
sample.c(13):(3) リマーク #15050: ループは並列化されませんでした: 並列依存関係が存在しています。
sample.c(19):(5) リマーク #15051: 並列依存関係: FLOW の依存関係が a 行 19 と a 行 19 の間に証明されました。

レポート生成オプションの詳細は、「最適化レポートオプション」を参照してください。

関連情報