Mímisbrunnr知恵の泉

← コンピュータ基礎 一覧

🎓 レベル:基礎 | 重要度:A(必須)

📎 前提:2進数・データ表現 | 関連:メモリ階層とキャッシュ性能の評価とアムダールの法則

要点(BLUF)

概念 ── プログラムは「命令の列」、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、対応ラボ参照)で実際のバイナリの命令列を覗くと、この対応が見えます。

仕組みの直観 ── なぜこの設計か

⚠️ よくある誤解・落とし穴

対応ラボ

cs-foundations-study/labs/01-02_disassemble.md(Linux: gcc -O0 -c した小さなCを objdump -d で逆アセンブルし、a+b がロード/加算/ストアに分かれるのを観察する手順)。

関連

第1章 計算機の構成 目次