🎓 レベル:基礎 | 重要度:A(必須)
📎 前提:2進数・データ表現 | 関連:メモリ階層とキャッシュ・性能の評価とアムダールの法則
要点(BLUF)
- CPUは「命令フェッチ → デコード → 実行 → 結果書き戻し」のサイクルを、クロックに合わせて延々と回しているだけ。プログラムとは、この機械が読む命令の列。
- 高速な作業台が レジスタ、計算する手が ALU、「次にどの命令を読むか」を覚えているのが プログラムカウンタ(PC)。
- 1命令ずつ待つと遅いので、流れ作業(パイプライン)で複数命令を重ねて処理する。理想は「毎クロック1命令完了」。
概念 ── プログラムは「命令の列」、CPUはそれを順に食べる
コンパイル後のプログラムは、メモリ上に並んだ機械語命令の列です。各命令は「レジスタAとBを足してCに入れろ」「このアドレスから読め」といった、ごく単純な操作を指定するビット列(2進数・データ表現)。CPUはこれを1つずつ取り出して実行します。
要するにCPUは、「今どこを読んでいるか(PC)」を頼りに命令を1つ取り、解釈し、実行し、PCを次へ進める――この繰り返しをする機械です。
仕組み① ── フェッチ-デコード-実行サイクル
1命令の処理は、おおまかに次の段階に分かれます。
flowchart LR
F["フェッチ(PCの指す命令をメモリから取得)"] --> D["デコード(命令の種類とオペランドを解読)"]
D --> X["実行(ALUで計算 / アドレス計算)"]
X --> M["メモリ(必要ならロード / ストア)"]
M --> W["書き戻し(結果をレジスタへ)"]
W --> F
各役者の役割:
| 部品 | 役割 |
|---|---|
| プログラムカウンタ(PC) | 次に実行する命令のアドレスを保持 |
| 命令レジスタ(IR) | 取得した命令そのものを保持 |
| 汎用レジスタ | 計算の作業場(メモリより圧倒的に速い・数十個程度) |
| ALU(算術論理演算器) | 加減算・論理演算・比較を行う実働部隊 |
| 制御装置 | デコード結果に従い各部へ「ゴー」信号を出す |
要するに、メモリは「倉庫」、レジスタは「手元の作業台」、ALUは「手」。倉庫から作業台へ運び、手で加工し、倉庫へ戻す、を命令単位で行います。
仕組み② ── クロックとパイプライン
CPUは クロック(一定間隔のパルス)に同期して動きます。3GHzなら毎秒30億回。素朴には「1命令=数クロック」かかりますが、それでは段階ごとにハードが遊びます(フェッチ中はALUが暇)。
そこで パイプライン:5段なら、命令1が「デコード」している間に命令2を「フェッチ」する、という流れ作業にします。
flowchart TB
subgraph t["クロックの流れ(左から右)"]
direction LR
c1["t1"] --- c2["t2"] --- c3["t3"] --- c4["t4"] --- c5["t5"]
end
i1["命令1: F D X M W"]
i2["命令2: F D X M W"]
i3["命令3: F D X M W"]
定常状態では毎クロック1命令が完成します。1命令の所要時間(レイテンシ)は変わりませんが、スループット(単位時間あたりの完成数)が段数倍に近づきます。工場のベルトコンベアと同じ発想です。
ただし流れは乱れます。分岐(if文)は「次にどの命令を読むか」が実行してみないと分からず、先読みが外れると詰めた命令を捨てる必要があります(分岐予測ミスのペナルティ)。前の命令の結果を次が使うデータ依存も待ちを生みます。この「乱れの管理」が現代CPUの腕の見せ所で、性能の議論(性能の評価とアムダールの法則)に直結します。
具体例 ── 1行のコードが複数命令になる
c = a + b; というC文は、典型的には次のような命令列にコンパイルされます(疑似アセンブリ)。
load r1, [a] ; aをメモリからレジスタr1へ
load r2, [b] ; bをr2へ
add r3, r1, r2 ; r1+r2 を r3 へ(ALU)
store [c], r3 ; r3 を c へ書き戻す
「1行=1命令」ではなく、ロード/演算/ストアに分解されます。逆アセンブラ(Linuxなら objdump -d、対応ラボ参照)で実際のバイナリの命令列を覗くと、この対応が見えます。
仕組みの直観 ── なぜこの設計か
- レジスタとメモリを分ける:ALUの隣に少数の超高速な置き場(レジスタ)を持つことで、毎演算ごとに遠い倉庫(メモリ)へ行かずに済む。これがメモリ階層(メモリ階層とキャッシュ)の出発点。
- パイプライン:ハードを遊ばせず、クロックを上げやすい(各段が短いから)。代償が分岐・依存によるストール。
- PCという1つのレジスタ:「次にどこを実行するか」を1点に集約することで、ジャンプ・関数呼び出し・割り込みが「PCを書き換える」という統一操作になる。これが 入出力とバス・割り込み の割り込みや、OSの文脈切り替え(プロセスとスレッド)の土台。
⚠️ よくある誤解・落とし穴
- 「クロックが速いCPUほど速い」→ 1命令に要するクロック数(CPI)や1命令で進む仕事量が違えば逆転する。比較は 性能の評価とアムダールの法則 のCPU性能式で。
- 「パイプラインは1命令を速くする」→ 速くするのはスループット。個々の命令のレイテンシはむしろ増えることもある。
- 「ソースコードの1行=1命令」→ ロード・演算・ストアに分かれる。最適化で消える行もある。
- 「分岐は無料」→ 予測ミスでパイプラインを捨てる。ホットループの条件分岐は性能に効く。
対応ラボ
cs-foundations-study/labs/01-02_disassemble.md(Linux: gcc -O0 -c した小さなCを objdump -d で逆アセンブルし、a+b がロード/加算/ストアに分かれるのを観察する手順)。
関連
- 速さの足を引っ張るメモリの話は メモリ階層とキャッシュ
- PC書き換えとして割り込みを捉えると 入出力とバス・割り込み が繋がる
- 「速い/遅い」を定量化するのが 性能の評価とアムダールの法則