自動並列化の概要

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

インテル(R) コンパイラーの自動並列化機能は、入力プログラムの直列部分を同等のマルチスレッド・コードに自動的に変換します。自動パラレライザーは、アプリケーション・ソース・コード中のループのデータフローを解析して、安全かつ効率的に並列実行可能なループに対するマルチスレッド・コードを生成します。

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

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

並列ランタイムコードは、ループの反復修正、スレッド・スケジューリング、および同期化の詳細を処理するような、OpenMP と同じランタイム機能を提供します。

OpenMP 宣言子はシリアル・アプリケーションを素早く並列アプリケーションに変換できますが、プログラマーは、並列処理を含み、適切なコンパイラー宣言子を追加するアプリケーション・コードの特定部分を明示的に識別する必要があります。

-parallel (Linux* および Mac OS* X) または /Qparallel (Windows*) オプションで起動された自動並列化は、並列処理を含むループ構造を自動的に識別します。コンパイル中、コンパイラーは、並列処理のためにコードシーケンスを別々のスレッドに自動的に分割しようと試みます。他にプログラマーにかかる負荷はありません。

IA-64 アーキテクチャーのみ: これらのオプションを指定することは -opt-mem-bandwith1 (Linux) または /Qopt-mem-bandwidth1 (Windows) を意味します。

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

例 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;

  // Thread 1

  for (i=0; i<50; i++)

    a[i] = a[i] + b[i] * c[i];

  // Thread 2

  for (i=50; i<100; i++)

    a[i] = a[i] + b[i] * c[i];

}

自動ベクトル化と並列化

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

次の例では、並列化とベクトル化による利点を明示的に得るためのコードの記述方法を示します。-parallel -xP (Linux) または /Qparallel /QxP (Windows) を使用して下記のコードをコンパイルすると、コンパイラーは外側のループを並列化して、最内ループをベクトル化します。

#include <stdio.h>

#define ARR_SIZE 500 //Define array

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);

      }

   }

}

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

vectorization.c(18) : (col. 6) remark: ループがベクトル化されました

vectorization.c(16) : (col. 3) remark: ループが自動並列化されました

ベクトル化の自動処理は、インテル(R) Pentium(R) プロセッサー、MMX テクノロジー Pentium プロセッサー、Pentium II プロセッサー、Pentium III プロセッサー、Pentium 4 プロセッサー・ベースのシステム上で動作するアプリケーションのパフォーマンスを向上します。

適切なオプションを選択することには、以下のような利点があります。

さらに、OpenMP 宣言子を各自のコードに追加するだけの簡単な処理で、プログラマーはシーケンシャル・プログラムを並列プログラムに変換できます。

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

#include <stdio.h>

#define ARR_SIZE 100 //Define array

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

// Initialize the arrays and matrices.

  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)

// Expresses the parallelism using the OpenMP pragma: parallel for.

// The pragma guides the compiler generating multithreaded code.

// Array arr_X, mb, b, and c are shared among threads based on OpenMP

// data sharing rules.Scalar num si specifed as private

// for each thread.

  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]); //prints values 0-ARR_SIZE-1

   }

}