C ユーザーズガイド

MP C (SPARC)

Sun MP C は、拡張 ANSI C コンパイラです。SPARC 共有メモリー付きマルチプロセッサマシン上で実行するコードをコンパイルすることができます。このプロセスは並列化と呼ばれています。コンパイル済みコードは、システム上の複数のプロセッサを用いて並列で実行することができます。

Sun WorkShop には、MP C の機能を使用するために必要なライセンスが含まれています。

この項では、MP C の概要と使用例を示すとともに、MP C で使用する環境変数、キーワード、プラグマ、オプションについて説明します。

MP C の使用例およびさらに詳しい参照情報については、/opt/SUNWspro/READMEs/ja/MPC.ps にある『MP C 技術白書』を参照してください。

概要

MP C コンパイラによって、並列化しても安全であると判断されたループに対する並列コードが生成されます。通常、これらのループは、独立して実行可能な繰り返しを持っています。このようなループに対しては、繰り返しの実行される順番や、並列に実行するかどうかといったことなどは、実行結果に影響しません。すべてではありませんが、ほとんどのベクトル処理用ループはこのような種類のループです。

C では別名が存在する (複数の変数が同一の実体である、または実体を指す) 可能性があるため、並列化の安全性を判断することは困難です。コンパイラの作業を容易にするために、MP C には別名の情報をコンパイラに渡すためのプラグマおよびポインタ修飾子が用意されています。

使用例

以下の例では、MP C コンパイラを使用して、並列化を制御する様子を示しています。ターゲットのプログラムを並列化するためには、-xautopar-xautopar オプションを以下のように使用します。


% cc -fast -xO4 -xautopar example.c -o example 

これを実行すると、example という実行可能ファイルが生成されます。ユーザーは通常の方法でこのファイルを実行することができます。

環境変数

マルチプロセッサ上で実行する場合には、以下のように環境変数を設定することが必要になります。


% setenv PARALLEL 2

この設定によって、プログラムが 2 個のスレッド上で実行されるようになります。ターゲットのマシンに複数の CPU が装備されていれば、この設定によって、それぞれのスレッドがそれぞれの CPU にマップされます。


% example

プログラムを実行すると、2 個のスレッドが生成され、各スレッド上でプログラムの並列化された部分が実行されるようになります。

キーワード

MP C ではキーワード _Restrict を使用することができます。詳細については、_Restrictの項を参照してください。

明示的な並列化およびプラグマ

すでに述べたように、並列化の効果がどれだけあるかをコンパイラだけで決めるには、情報が不十分なことがあります。MP C では、プラグマをサポートしており、コンパイラだけでは不可能なループの並列化を効率よく実行することができます。

直列プラグマ

直列プラグマには 2 通りあり、どちらも for ループに適用されます。

  1. #pragma MP serial_loop

  2. #pragma MP serial_loop_nested


#pragma MP serial_loop

serial_loop プラグマによって、次に存在する for ループを暗黙的または自動的に並列化しないことが指示されます。


#pragma MP serial_loop_nested

serial_loop_nested プラグマによって、次にある for ループ、およびその for ループの中で入れ子になっている for ループを暗黙的または自動的に並列化しないことが指示されます。なお、serial_loop_nested のスコープは、このプラグマが適用されるループの範囲を越えることはありません。

並列プラグマ

並列プラグマは 1 つだけあります。

#pragma MP taskloop (オプション)

MP taskloop プラグマは、オプションとして、以下の引数を取ることができます。

MP taskloop プラグマ 1 つに対して指定できるオプションは 1 つだけです。ただし、複数のプラグマの効果を重ねて、ソースコード内の現在のブロックにある次の for ループに適用することができます。


#pragma MP taskloop maxcpus(4)
#pragma MP taskloop shared(a,b)
#pragma MP taskloop storeback(x)

これらのオプションは、for ループの前に複数回指定できます。オプションが衝突を起こす場合には、コンパイラによって警告メッセージが出力されます。

for ループの入れ子

MP taskloop プラグマは、現在のブロック内にある次の for ループに適用されます。MP C によって並列化された for ループに入れ子は存在しません。

並列化の適切性

MP taskloop プラグマによって、ループを並列化するように指示されます。

不規則なフロー制御や、一定しない増分による繰り返しを持ったループに対しては、正当な並列化を実行できません。たとえば、setjmp、longjmp、exit、abort、return、goto、ラベル、break を含んだ for ループは並列化に適しません。

特に重要なこととして、繰り返し間の依存性を持った for ループでも、明示的に並列化できる点に注意してください。すなわち、このようなループに対して MP taskloop プラグマが指定されていると、for ループが並列化に適していないと判断されない限り、単にこの指示に従ってコンパイルを実行してしまいます。このような明示的な並列化を行なった場合は、不正確な結果が発生しないかを確認してください。

1 つのループに対して serial_loop または serial_loop_nestedtaskloop の両方のプラグマが指定されている場合には、最後の指定が優先的に使用されます。

以下の例を考えてみましょう。


      #pragma MP serial_loop_nested
          for (i=0; i<100; i++) {
         # pragma MP taskloop
           for (j=0; j<1000; j++) {
                   ...
          }
         }

この例では、i ループは並列化されませんが、j ループは並列化が可能です。

プロセッサの数

#pragma MP taskloop maxcpus (<プロセッサの数>) は、指定が可能であれば、現在のループに対して使用される CPU の数を指定します。

maxcpus に指定する値は正の整数でなければなりません。maxcpus が 1 であれば、指定されたループは直列に実行されます。なお、maxcpus を 1 に指定した場合には、doserial 指令をしたことと同等になる点に注意してください。また、maxcpus の値か PARALLEL 環境変数のどちらか小さい方の値が使用されます。環境変数 PARALLEL が指定されていない場合には、この値に 1 が指定されているものとして扱われます。

1 つの for ループに複数の maxcpus プラグマが指定されている場合には、最後に指定された値が優先的に使用されます。

変数の分類

ループに使用される変数は、「スレッド固有」、「共有」、「縮約」または「読み取り専用」のどれかに分類されます。1 つの変数は、これらの種類のうち 1 つにのみ属します。 変数の種類を縮約または読み取り専用にするには、明示的にプラグマで指示しなければなりません。詳しくは、「縮約変数」および 「読み取り専用変数」を参照してください。変数を「スレッド固有」または「共有」にするには明示的にプラグマを使用するか、または以下のスコープの規則にもとづいて決まります。

スレッド固有変数と共有変数のデフォルトのスコープの規則

スレッド固有変数は、for ループのある繰り返しを処理するためにそれぞれの CPU が専用に使用する値を保持します。別の言い方をすれば、for ループのある繰り返しでスレッド固有変数に割り当てられた値は、そのループの別の繰り返しを処理している CPU からは見えません。これに対して共有変数は、このような特性を持っていません。共有変数とは、現在のループ繰り返しを処理しているすべての CPU から現在の値にアクセスできる変数のことです。ループのある繰り返しを処理している 1 つの CPU が共有変数に代入した値は、そのループの別の繰り返しを処理している CPU からでも見ることができます。共有変数を参照しているループを #pragma MP taskloop 指令によって明示的に並列化する場合には、値の共有によって正確性に問題が起きないことを確認しなければなりません (競合条件の確認など)。明示的に並列化されたループの共有変数へのアクセスおよび更新では、コンパイラによる同期はとられません。

明示的に並列化されたループの解析において、変数がスレッド固有と共有のどちらであるかを決定するために、以下の「デフォルトのスコープの規則」が使用されます。

コンパイラは、共有変数に対するアクセスの同期を一切実行しないので、たとえば、配列参照を含んだループに対して MP taskloop プラグマを使用する前には、十分な考察が必要になります。このように明示的に並列化されたループで、繰り返し間でのデータ依存性がある場合には、並列実行を行うと正しい結果を得られないことがあります。コンパイラによって、このような潜在的な問題を検出し、警告メッセージを出力することもできますが、一般的にこれを検出することは非常に困難です。なお、共有変数に対する潜在的な問題を持ったループでも、明示的に並列化を指示されると、コンパイラはこの指示に従います。

スレッド固有変数

#pragma MP taskloop private (<スレッド固有変数のリスト>) は、現在のループでスレッド固有変数として扱われる必要のあるすべての変数を指定します。ループで使用されている別の変数は、それ自体が明確に共有、読み取り専用、ストアバック、または縮約であることが指定されていない限り、デフォルトのスコープの規則に従って、共有またはスレッド固有のどちらかに分類されます。

スレッド固有変数とは、それ自体の値がループのある繰り返しを処理する CPU 専用になっている変数のことです。別の言い方をすれば、ループのある繰り返しを処理している CPU によってスレッド固有変数に代入された値は、そのループの別の繰り返しを処理している CPU から見ることはできません。スレッド固有変数には、ループの繰り返しの開始時に初期値は代入されず、繰り返し内で最初に使用される前に、その繰り返し内で値が代入されなければなりません。値が設定される前にその値を参照するように明確に宣言されたスレッド固有変数を持つループを実行すると、その動作は保証されません。

共有変数

#pragma MP taskloop (<共有変数のリスト>) は、現在のループで共有変数として扱われる必要のあるすべての変数を指定します。ループで使用されている別の変数は、それ自体が明確にスレッド固有、読み取り専用、ストアバック、または縮約であることが指定されていない限り、デフォルトのスコープの規則に従って、共有またはスレッド固有のどちらかに分類されます。

共有変数とは、ある for ループの繰り返しを処理しているすべての CPU から現在の値を見ることのできる変数のことです。ループのある繰り返しを処理している CPU が共有変数に代入した値は、そのループの別の繰り返しを処理している CPU からでも見ることができます。

読み取り専用変数

#pragma MP taskloop readonly (<読み取り専用変数リスト>) は、現在のループで読み取り変数として扱われる必要のあるすべての変数を指定します。

読み取り専用変数とは、共有変数の特殊なクラスのことで、ループのどの繰り返しでも、その値を変更できません。変数を読み取り専用として指定すると、ループの繰り返しを処理しているそれぞれの CPU に対して、個々に変数値のコピーが使用されます。

ストアバック変数

#pragma MP taskloop storeback (<ストアバック変数リスト>) は、現在のループでストアバック変数として扱われる必要のあるすべての変数を指定します。

ストアバック変数とは、ループの中で変数値が計算され、その値がループの終了後に使用される変数のことです。ループの最後の繰り返しにおけるストアバック変数の値が、ループの終了後に利用可能になります。このような変数は、その変数が明示的な宣言やデフォルトのスコープ規則によってスレッド固有変数となっている場合には、この指令を使用して明示的にストアバック変数として宣言するとよいでしょう。

なお、ストアバック変数に対する最終的な書き戻し操作は、明示的に並列化されたループの最後の繰り返しにおいて、その中で実際に値が変更されたかどうかには関係なく実行される点に注意してください。すなわち、ループの最後の繰り返しを処理する CPU と、ストアバック変数の最終的な値を保持している CPU とは、異なる可能性があります。

以下の例を考えてみましょう。


       #pragma MP taskloop private(x)
       #pragma MP taskloop storeback(x)
        for (i=1; i <=n; i++) {
            if (...) {
                x =...
          }
      }
        printf ("%d", x);

上述の例では、printf() 呼び出しによって出力されるストアバック変数 x の値は、i のループを直列に実行した場合の出力値とは異なる可能性があります。なぜならば、明示的に並列化されたループでは、ループの最後の繰り返し (すなわち i==n のとき) を処理し、x に対してストアバック操作を行う CPU は、現在最後に更新された x の値を保持する CPU とは同じでないことがあるからです。このような潜在的な問題に対し、コンパイラは警告メッセージを出力します。

明示的に並列化されたループでは、配列として参照される変数をストアバック変数とは扱いません。したがって、このような変数にストアバック処理が必要な場合 (たとえば、配列として参照される変数がスレッド固有変数として宣言されている場合) には、その変数を <ストアバック変数リスト> に含める必要があります。

savelast

このプラグマによって、ループ内のすべてのスレッド固有変数がストアバック変数として扱われます。このプラグマの構文を以下に示します。


#pragma MP taskloop savelast

各変数をストアバック変数として宣言するときには、それぞれのスレッド固有変数をリストするよりも、この形式が便利であることがよくあります。

縮約変数

リストにあるすべての変数は、そのループに対しての縮約変数として扱われます。縮約変数とは、ループのある繰り返しを処理している個々の CPU によって、その値を部分的に計算され、最終値がすべての部分値から計算される変数のことをいいます。縮約変数リストにより、そのループが縮約ループであることをコンパイラに示し、適切な並列縮約用のコードを生成できるようにします。以下の例を考えてみましょう。


        #pragma MP taskloop reduction(x)
             for (i=0; i<n; i++) {
                 x = x + a[i];
          }

ここでは、変数 x が (和の) 縮約変数であり、i ループが (和の) 縮約ループになっています。

スケジューリングの制御

MP C コンパイラには、指定されたループのスケジューリングを戦略的に制御するために、taskloop プラグマと同時に使用するいくつかのプラグマが用意されています。このプラグマの構文を以下に示します。


#pragma MP taskloop schedtype (
<スケジューリング型>)

このプラグマによって、並列化されたループをスケジュールするための <スケジューリング型> を指定することができます。<スケジューリング型> には、以下のいずれかを指定できます。

コンパイラオプション

MP C では次のコンパイラオプションを使用することができます。これらのオプションの詳細は、第 2 章「cc コンパイラオプション」で説明してあります。