はじめに
CPUの高速化には「パイプライン処理」が欠かせませんが、それに伴って発生するのが ハザード(hazard) です。その中でも特にやっかいなのが、 分岐命令によって次に実行すべき命令が分からなくなる「分岐ハザード(branch hazard)」 です。
本記事では、分岐ハザードの概要、なぜ問題なのか、どのように対策されているのかをわかりやすく解説します。
パイプライン処理とは?
パイプライン処理とは、 1つの命令を複数の工程(ステージ)に分割し、それぞれのステージを別の命令が同時に処理する ことで、CPU全体として並列処理を実現する技術です。まるで工場のベルトコンベアのように、命令を次々と流れ作業で片付けていきます。
命令1: フェッチ → デコード → 実行 → ...
命令2: フェッチ → デコード → ...
命令3: フェッチ → ...
一般的な5ステージのパイプラインは以下のようになります。
- IF (Instruction Fetch): メモリから命令を読み込む(フェッチ)
- ID (Instruction Decode): 命令を解読する(デコード)
- EX (Execute): 命令を実行する
- MEM (Memory Access): メモリへの読み書きを行う
- WB (Write Back): 結果をレジスタに書き込む
理想的な状況では、クロックが進むごとに各ステージが同時に動き、CPUは1クロックあたり1命令を完了させる高いスループットを達成します。
分岐ハザードとは?
パイプライン処理は、次に実行する命令が分かっている場合に最も効率的に機能します。しかし、分岐命令が登場すると、その前提が崩れます。
分岐命令が登場すると、その結果が分かるまで次に何を実行すべきか確定できません。
それでもCPUは高速化のため、分岐の結果を待たずに命令を先読みします。
100: BEQ R1, R2, 200 ; R1とR2が等しければ、アドレス200へジャンプ
104: ADD R3, R4, R5 ; 分岐しない場合に実行する命令
...
200: SUB R6, R7, R8 ; 分岐した場合に実行する命令
BEQ命令(Branch if Equal) の結果、つまりR1とR2が等しいかどうかは、EXステージ(実行段階)まで進まないと分かりません。
しかし、パイプラインは止まりません。CPUはBEQ命令の結果を待たずに、次のADD命令をIFステージ、IDステージへと進めてしまいます。
もし、EXステージで「分岐する(ジャンプする)」と判明したらどうなるでしょうか?
- 大失敗! パイプラインに取り込んでしまったADD命令は、本来実行すべきではなかった命令です。
- 処理の破棄(フラッシュ): CPUは、この間違って読み込んだ命令をすべて捨てなければなりません。
- 正しい命令の再取得(再フェッチ): そして、正しい分岐先(アドレス 200 の SUB 命令)から、改めて命令をフェッチし直します。
この「分岐の結果が分かるまで次に実行すべき命令が確定できず、パイプラインに無駄な待ち時間(ストール)や処理の破棄(フラッシュ)が発生する」こと、これが分岐ハザードの正体です。
- ✅ フェッチした命令をすべて破棄(フラッシュ)
- ✅ 再フェッチしてやり直し
- ✅ パフォーマンス低下
分岐ハザードが「ひどい問題」と言える理由
分岐ハザードが「CPU設計における最大の頭痛の種」とまで言われる理由は、主に3つあります。
1. パイプラインが深いほどペナルティが甚大
現代の高性能CPUでは、処理をさらに細分化して10段、20段、あるいはそれ以上の深いパイプラインを持つのが一般的です。パイプラインが深ければ深いほど、分岐をミスしたときに破棄しなければならない命令の数が増え、失われるサイクル(ペナルティ)が致命的なレベルになります。
2. 分岐はプログラムの至る所に存在する
if-elseによる条件分岐、forやwhileによるループ、関数の呼び出しなど、我々が書くコードは分岐命令の塊です。研究によれば、全実行命令の15%〜20%が分岐命令であるとされており、その影響は決して無視できません。
3. 投機的実行の複雑化とセキュリティリスク
後述する対策「投機実行」は、間違った場合の「状態の巻き戻し(ロールバック)」機構を必要とし、CPUの設計を非常に複雑にします。また、この仕組みが、近年話題になったSpectreやMeltdownといったセキュリティ脆弱性の根源にもなりました。
分岐ハザードへの対策
1. 分岐遅延スロット (Branch Delay Slot)
古典的なRISCアーキテクチャ(MIPSなど)で採用された、ユニークな解決策です。
- 「分岐命令の直後にある1命令だけは、分岐するかどうかにかかわらず必ず実行する」というルールを設ける
- 分岐の結果を待つ間にパイプラインにできてしまう「穴」を、その「必ず実行される」命令で埋めることで、CPUの遊休時間を減らす
コンパイラは、分岐命令の前にあり、分岐の結果に影響されない安全な命令を見つけて、この「遅延スロット」に移動させる最適化を行います。しかし、パイプラインが深化した現代では、1スロットではペナルティを埋めきれないことや、コンパイラの負担が大きいことから、主流ではなくなっています。
2. 分岐予測 (Branch Prediction)
現在主流となっている対策です。「コケる前に杖を突く」戦略で、分岐命令がどちらに進むかを積極的に予測し、予測した側の命令をパイプラインに送り込みます。
- 静的分岐予測: 「ループのような後方への分岐は常に成立(Taken)と予測する」「常に分岐しない(Not Taken)と予測する」など、プログラムの構造から決まる単純なルールで予測する
- 動的分岐予測: CPU内に分岐履歴テーブル (BHT: Branch History Table) という小さなメモリを持ち、個々の分岐命令が過去にどちらに分岐したかを記録する
さらに、分岐先のメモリアドレスをキャッシュしておく分岐先バッファ (BTB: Branch Target Buffer) と組み合わせることで、「分岐するかどうか」と「どこへジャンプするか」を高速に予測します。
3. 投機実行 (Speculative Execution)
分岐予測をさらに一歩進めた、非常にアグレッシブな手法です。
- 分岐予測の結果を信じて、確定する前に命令をどんどん実行してしまう
- 予測が正しければ、計算結果をそのまま使い、大幅な時間短縮になる
- 予測が外れたら、投機的に実行したすべての命令の結果を きれいに破棄(ロールバック) し、何もなかったかのように正しいパスの実行を再開する
この「見切り発車」はハイリスク・ハイリターンですが、分岐予測の精度が90%以上と非常に高いため、トータルでは絶大なパフォーマンス向上をもたらします。現代の高性能CPUの根幹をなす技術です。
おわりに
分岐ハザードはCPUの命令実行を効率的に行ううえで避けて通れない問題です。
「たった1つのif文」のために、CPUが10命令も無駄にする世界——それがコンピュータアーキテクチャの面白さでもあります。
CPUの奥深さに触れる第一歩として、ぜひ知っておきたい概念ですね。