7839

雑草魂エンジニアブログ

並列処理(2)静的最適化

現在、CSの勉強のために、コンピュータアーキテクチャ (電子情報通信レクチャーシリーズ)を読んでいる。

コンピューターの性能向上には、並列処理が必要不可欠である。前回に引き続き、静的最適化に関してまとめていく。静的最適化(static optimization)とは、プロセッサを効率良く動かすために、あらかじめ機械語プログラムを最適化することを指す。一般的にはコンパイラで行われる。

機械語プログラムと命令間依存性

ハザードを出来るだけ減らすためにコンパイラができることとして、以下が挙げられる。

  1. 依存関係を解消したり減らしたりする
    • ループアンローリング
      • 複数のループを統合して1つのループにすることで、分岐命令によるハザードをなくす手法
    • トレーススケジューリング
      • 分岐予測をして複数の基本ブロックを統合し、制御依存を減らす手法
  2. 依存関係のある命令どうしをプログラムの中で離れた位置に置く
    • ソフトウェアパイプライニング
      • ループ間にまたがって命令を移動し、依存関係のある命令どうしの距離を離すことでハザードを起こりにくくする手法

但し、上記はプログラムの意味を変えない範囲で行わなければならない。

例として、配列aのすべての要素に5を加えるプログラムを考える。

for (i=0; i < 100; i++) {
  a[i] = a[i] + 5;
}

これをコンパイルすると、以下のアセンブリ言語を得る。

  • 実行環境は以下を想定する。
    • ALU:2つ
    • 命令フェッチ・結果書き戻し:同時に2命令実行可能
    • レジスタ:同時に4出力2入力

上記のプログラムにおいて、blt r1, r2, ForLoopの分岐命令で制御ハザードが発生する。分岐命令があるたびに、3クロックのストールが生じてしまう。また、並列処理を行う場合には、分岐命令をまたぐ並列化は通常行われない。

但し、今回のループにおいて、if (r1 < r2)はr1を1ずつインクリメントしているだけのため、r1=100になる最後以外は分岐予測(常に分岐しないと予測)が100%当たるため、制御ハザード解消のために分岐予測を行う。

ループ1周は、9クロックで実行される。

ループアンローリング

ループアンローリングは、複数のループを統合して1つのループにすることで、分岐命令によるハザードをなくす手法である。今回の場合、100回のループを4つのループにまとめて1つにする。アセンブリ言語は以下の通りである。

パイプラインは以下の通りである。

ループ4周分で13クロックで実行される。

先ほどの分岐予測の場合と比較して、9クロック×4周=36クロックとなるため、36 / 13 = 2.77倍の性能が出ている。

ループアンローリングによる効率改善の理由

  • 分岐命令の削減(bltが3つ減ったこと)
    • 分岐命令は並列化しないので、bltを3つ削減することで、3クロック削減できる
  • lw, addi, swをそれぞれ4つずつまとめることによるデータハザードの解消
    • 依存関係がある命令を離すことでデータハザードが起こらなくすることができる

ソフトウェアパイプライニング

ソフトウェアパイプライニングは、ループ間にまたがって命令を移動し、依存関係のある命令どうしの距離を離すことでハザードを起こりにくくする手法である。また、ソフトウェアパイプライニングとループアンローリングは組み合わせて用いることができる。

今回においては、ループの最初で依存関係のある命令が連続している。

この部分をループをまたがって処理できるように移動させる。すなわち、ループの中ではa[i]計算済みの値を代入し、次のループi+1番目の計算を行うようにする。以下のようなプログラムになる。

  • r5 = a[0] + 5
  • r4 = a[1]
  • i = 0の時
    • a[0] = r5 (= a[0] + 5)
    • r5 = r4 + 5 (= a[1] + 5)
    • r4 = a[2]
  • i = 1の時
    • a[1] = r5 (= a[1] + 5)
    • r5 = r4 + 5 (= a[2] + 5)
    • r4 = a[3]
  • i = 2の時
    • a[2] = r5 (= a[2] + 5)
    • r5 = r4 + 5 (= a[3] + 5)
    • r4 = a[4]

アセンブリ言語は以下の通りである。

パイプラインは以下の通りである。

ループ1周は、8クロックで実行される。

先ほどの分岐予測の場合と比較して、9 / 8 = 1.13倍の性能が出ている。

ソフトウェアパイプライニングによる効率改善の理由

  • lw, swの命令どうしの距離を離すことによるデータハザードの解消

トレーススケジューリング

トレーススケジューリングは、分岐予測をして複数の基本ブロックを統合し、制御依存を減らす手法である。

  • 基本ブロック:ある分岐命令の直後から次の分岐命令までの命令列
  • 制御フローグラフ:プログラムを基本ブロックの間の依存関係として表現したもの

トレーススケジューリングの手順

  1. 実行履歴(プロファイル / profile)などによって、実行される確率の最も高い分岐パターンを調べる。
  2. (1)のパターンの上にある基本ブロックを統合する。これをトレース(trace)と呼ぶ。
  3. トレースに命令スケジューリングを施すことで、トレース内の実行効率を高める。トレース内にも分岐命令はあるが、「分岐命令を超えた命令移動」も行う。
  4. (3)によってトレース以外に分岐する場合に生じる不都合を防ぐため、分岐先の入り口に補正用のコードを入れる。これをブックキーピング(bookkeeping)と呼ぶ。
  5. トレースを除いた制御フローグラフにおいて、(1)~(4)を繰り返す。

今回の配列のループにおいては、条件分岐が複数あるわけではないので、トレーススケジューリングを用いることはできない。

まとめ

コンパイルによる静的最適化に関して、具体的な例を通してどのような手法があるのか、またそのやり方含め、理解を深めることができた。

関連書籍