命令を複数ステージに分けて流れ作業で並行実行し処理を高速化する方式
パイプライン処理とは、1つの命令の実行をいくつかの段階(ステージ)に分け、複数の命令を少しずつずらして同時に処理する高速化の手法です。
身近な例で考えると、工場の流れ作業(ベルトコンベア)そのものです。1人が1台の製品を完成まで作るのではなく、「組み立て係」「塗装係」「検査係」が並んで、製品が流れてくるたびに自分の工程だけを担当します。1台目を塗装している間に2台目を組み立てられるので、全体の処理が止まらず効率が上がります。
上のツールで▶ボタンを押すと、命令1がIF→ID→…と進む間に、命令2、命令3が1クロックずつ遅れて投入され、表が階段状に埋まっていく様子が見られます。色のついたマスがその命令の現在のステージです。
古典的なパイプラインでは、命令の実行を次の5つのステージに分けます。
・IF(命令フェッチ):メモリから命令を取り出す
・ID(命令デコード):命令を解読し、必要なデータ(オペランド)を読む
・EX(実行):ALU(演算装置)で計算を行う
・MEM(メモリアクセス):必要に応じてメモリを読み書きする
・WB(ライトバック):結果をレジスタに書き戻す
ポイントは、各ステージが別々の装置(ハードウェア)で動いていることです。だから命令1がEX(実行)をしている同じクロックで、命令2はID(デコード)、命令3はIF(フェッチ)を進められます。5段のパイプラインがフル稼働すれば、最大で5つの命令が同時に処理されている状態になります。
ツールの表で、縦に同じクロック(同じT列)を見てください。フル稼働の区間では、その列に IF・ID・EX・MEM・WB が縦に並びます。これが「5命令が同時に別ステージを処理している」状態です。
パイプラインの実行時間は、次の公式で求められます。
実行時間 = (n + ステージ数 − 1) × クロック周期
n は命令数、ステージ数は段数(ここでは5)、クロック周期は1クロックの時間です。「+(ステージ数−1)」の部分は、最初にパイプラインが満杯になるまでの充填の遅れを表します。
具体例: 5 命令・5ステージの場合
パイプライン = 5 + (5 − 1) = 9 クロック
逐次実行 = 5 × 5 = 25 クロック
─────────────
短縮: 25 → 9 クロック
命令数 n が大きくなるほど、充填の遅れ(+4)が相対的に小さくなり、1クロックあたり約1命令が完了する理想に近づきます。これがパイプラインの威力です。なお、後の命令が前の結果を待つ「ハザード」が起きると、この理想は崩れてストール(待ち)が入ります。
パイプラインは「前の命令の結果をすぐ次の命令が使える」前提で動きますが、実際にはそうならない場合があります。このような「流れを止める原因」をハザード(hazard=危険・障害)と言います。ハザードが起きると命令が途中でストール(stall=一時停止)し、空白クロック(バブル)が挿入されます。
ハザードには主に3種類あります。
・データハザード:後の命令が前の命令の計算結果を使うとき、結果がまだ出ていない(上の図の例)。最もよく起きる。
・制御ハザード:「条件分岐(if文に相当する命令)」の結果が出るまで、次にどの命令を取ってくるか分からない。分岐の行き先が確定するまで後続の命令を止めるか、先読みして違えば捨てる。
・構造ハザード:複数の命令が同じハードウェア(たとえばメモリ)を同じクロックで使おうとして衝突する。
なぜハザードは問題なのか。ストールが1クロック入るたびに、本来の公式より実行時間が長くなります。たとえば5命令・5ステージで本来9クロックのところ、2クロックのストールが入れば11クロックかかります。現代のCPUはハザードを減らすために「データを先読みして早めに渡す(フォワーディング)」などの工夫を組み合わせています。
記号の意味:n=命令数、k=ステージ数(パイプラインの段数)
例) n=10命令、k=5ステージ の場合
逐次実行 = 10 × 5 = 50 クロック
パイプライン = 10 + (5-1) = 14 クロック
短縮率 = 50 ÷ 14 ≒ 3.6倍速
命令数 n が大きくなるほど「k-1」の初期充填コストは相対的に小さくなり、理論上の最大速度はk倍(ステージ数倍)に近づきます。n が十分大きければ「1クロックに1命令が完了」する状態が続き、逐次実行のk倍のスループット(処理量)が得られます。ただし上で学んだハザードがあるため、実際は理論値より低くなります。
上のビジュアライザでシナリオを変えて「逐次実行なら〇〇クロック」と「パイプラインなら〇〇クロック」を比べてみてください。n が増えるほど差が広がっていく様子が確認できます。