JavaScript is required to for searching.
ナビゲーションリンクをスキップ
印刷ビューの終了
Oracle Solaris Studio 12.2: C ユーザーガイド
search filter icon
search icon

ドキュメントの情報

はじめに

1.  C コンパイラの紹介

2.  C コンパイラ実装に固有の情報

3.  C コードの並列化

3.1 概要

3.1.1 使用例

3.2 OpenMP に対する並列化

3.2.1 OpenMP の実行時の警告の処理

3.3 環境変数

3.3.1 PARALLEL または OMP_NUM_THREADS

3.3.2 SUNW_MP_THR_IDLE

3.3.3 SUNW_MP_WARN

3.3.4 STACKSIZE

3.3.5 並列コードでの restrict の使用

3.4 データの依存性と干渉

3.4.1 並列実行モデル

3.4.2 固有スカラーと固有配列

3.4.3 ストアバック変数の使用

3.4.4 縮約変数の使用

3.5 処理速度の向上

3.5.1 アムダールの法則

3.5.1.1 オーバーヘッド

3.5.1.2 ガスタフソンの法則

3.6 負荷バランスとループのスケジューリング

3.6.1 静的 (チャンク) スケジューリング

3.6.2 セルフスケジューリング

3.6.3 ガイド付きセルフスケジューリング

3.7 ループの変換

3.7.1 ループの分散

3.7.2 ループの融合

3.7.3 ループの交換

3.8 別名と並列化

3.8.1 配列およびポインタの参照

3.8.2 制限付きポインタ

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

3.8.3.1 直列プラグマ

3.8.3.2 並列プラグマ

for ループの入れ子

並列化の適切性

プロセッサの数

変数の分類

スレッド private 変数と shared 変数のデフォルトのスコープの規則

private 変数

shared 変数

readonly 変数

storeback 変数

savelast

reduction 変数

スケジューリングの制御

3.9 メモリーバリアー組み込み関数

4.  lint ソースコード検査プログラム

5.  型に基づく別名解析

6.  ISO C への移行

7.  64 ビット環境に対応するアプリケーションへの変換

8.  cscope: 対話的な C プログラムの検査

A.  機能別コンパイラオプション

B.  C コンパイラオプションリファレンス

C.  ISO/IEC C 99 の処理系定義の動作

D.  C99 でサポートされている機能

E.  ISO/IEC C90 の処理系定義の動作

F.  ISO C データ表現

G.  パフォーマンスチューニング

H.  K&R Solaris Studio C と Solaris Studio ISO C の違い

索引

3.7 ループの変換

コンパイラは、プログラム中のループを並列に実行できるようにするために、ループ再構成のための変換を数回実行します。この変換のいくつかは、シングルプロセッサ上でのループの実行速度も向上させます。コンパイラが実行する変換を次で説明します。

3.7.1 ループの分散

ループには、並列に実行できる文とできない文とが存在することがあります。通常、並列実行できない文はごく少数です。ループの分散によって、これらの文を別のループに移動し、並列実行可能な文だけから成るループを作ります。これを次の例で説明します。

例 3-14 ループの分散に適したコード

for (i=0; i < n; i++) {
    x[i] = y[i] + z[i]*w[i];               /* S1 */
    a[i+1] = (a[i-1] + a[i] + a[i+1]/3.0;  /* S2 */
    y[i] = z[i] - x[i];                    /* S3 */
}

配列 xywaz が重なりあっていないと仮定すると、文 S1 および S3 を並列実行することはできますが、文 S2 はできません。このループを異なる 2 個のループに分割すると次のようになります。

例 3-15 分散されたループ

/* L1: parallel loop */
for (i=0; i < n; i++) {
    x[i] = y[i] + z[i]*w[i];              /* S1 */
    y[i] = z[i] - x[i];                   /* S3 */
}
/* L2: sequential loop */
for (i=0; i < n; i++) {
    a[i+1] = (a[i-1] + a[i] + a[i+1]/3.0; /* S2 */
}

この変換のあと、前述のループ L1 には並列実行を妨害する文が含まれていないので、これを並列実行できるようになります。ところが、2 番目のループ L2 は元のループの並列実行できない部分を引き継いだままです。

ループの分散は、常に効果があって安全に実行できるとはかぎりません。コンパイラは、この効果と安全性を確認するための解析を実行します。

3.7.2 ループの融合

ループが小さい、すなわちループでの作業量が少ないと、大幅にパフォーマンスを向上させることはできません。これは、ループでの作業量に比べて、並列ループを起動するときのオーバーヘッドが大きくなるためです。このような状況では、コンパイラはループの融合を使用して、いくつかのループを 1 つの並列ループに融合し、ループを大きくします。同じ回数の繰り返しを行うループが隣接していると、ループの融合は簡単にしかも安全に行われます。次の例を考えてみましょう。

例 3-16 作業量の少ないループ

/* L1: short parallel loop */
for (i=0; i < 100; i++) {
    a[i] = a[i] + b[i];        /* S1 */
}
/* L2: another short parallel loop */
for (i=0; i < 100; i++) {
    b[i] = a[i] * d[i];        /* S2 */
}

この例では、2 個の小さなループが隣どうしに記述されていて、次のように安全に融合することができます。

例 3-17 融合された 2 つのループ

/* L3: a larger parallel loop */
for (i=0; i < 100; i++) {
    a[i] = a[i] + b[i];       /* S1 */
    b[i] = a[i] * d[i];       /* S2 */
}

これによって、並列ループの実行によるオーバーヘッドを半分にすることができます。ループの融合は、別の場合にも役に立ちます。たとえば、同じデータが 2 個のループで参照されている場合には、この 2 個のループを融合すると、参照を局所的なものにすることができます。

ただし、ループの融合は常に安全に実行できるとはかぎりません。ループの融合によって、元々存在していなかったデータの依存関係が生成されると、実行結果が正しくなくなることがあります。次の例を考えてみましょう。

例 3-18 安全でない融合の例

/* L1: short parallel loop */
for (i=0; i < 100; i++) {
    a[i] = a[i] + b[i];      /* S1 */
}
/* L2: a short loop with data dependence */
for (i=0; i < 100; i++) {
    a[i+1] = a[i] * d[i];    /* S2 */
}

「3.7.2 ループの融合」でループの融合が実行されると、文 S2 から S1 に対するデータの依存性が生成されます。実際に、文 S1 の右辺にある a[i] の値が、文 S2 で計算されるものになります。これはループが融合されないと起こりません。コンパイラは、ループの融合を実行すべきかどうかを判断するために解析を行い、安全性と有効性を確認します。場合によっては、任意の数のループを融合できることがあります。このような方法でループの作業量を多くすると、並列実行が十分に有効であるようなループを生成することができます。

3.7.3 ループの交換

入れ子になっているループのもっとも外側のループを並列化すると、発生するオーバーヘッドが小さいために、一般に大きな効果が期待できます。しかし、そのループに依存性がある場合は、並列化することは安全ではありません。次の例で説明します。

例 3-19 並列化できない入れ子のループ

for (i=0; i <n; i++) {
    for (j=0; j <n; j++) {
            a[j][i+1] = 2.0*a[j][i-1];
    }
}

この例では、添字変数 i を持つループは、連続する 2 つの繰り返しで依存関係があるために、並列化することができません。ただし、2 つのループを交換することができるため、交換すると並列ループ (j のループ) が今度は外側のループになります。

例 3-20 交換されたループ

for (j=0; j<n; j++) {
    for (i=0; i<n; i++) {
            a[j][i+1] = 2.0*a[j][i-1];
    }
}

この結果生成されたループでは並列作業の分散に対するオーバーヘッドが 1 回で済むのに対して、元のループでは、n 回必要でした。コンパイラは、これまで説明したように、ループの交換をするかどうか決定するための解析を行い、安全性と有効性を確認します。