第 9 章 コンピュータアーキテクチャ¶
まえがき — 機械の中身を覗く¶
print("Hello") と書いて Enter を押すと、なぜ画面に文字が表示されるの? CPU って具体的に何をしているの? なぜ「キャッシュに乗るデータ」は速いの? なぜ「分岐予測ミス」がパフォーマンスを左右するの?
これらの疑問に答えるのが コンピュータアーキテクチャ です。
ハードウェアを知らないプログラマは、性能チューニングや低レイヤのバグで詰まります。逆に「メモリは平面ではない」「キャッシュミスは命取り」「分岐予測ミスは数十サイクルかかる」を体感していると、書くコードが変わります。
🎯 章の目標
- 数の表現(2 進・2 の補数・浮動小数点)と論理回路の組み立てを理解する
- CPU の構成(ALU・レジスタ・データパス)を辿り、命令がどう実行されるかを説明できる
- パイプライン・キャッシュ・仮想記憶など現代 CPU の高速化機構を体系的に把握する
- 並列性(命令・データ・スレッド)の階層を理解する
9.1 なぜハードウェアを知るべきか¶
9.1.1 性能の違いは桁違い¶
同じ「配列の合計を求める」でも、メモリアクセスの順序を変えるだけで 10 倍 性能が変わることがあります。
// 早い: 連続アクセス(キャッシュ効率良い)
for (int i = 0; i < N; i++) sum += a[i];
// 遅い: 飛び飛びアクセス
for (int i = 0; i < N; i += 64) sum += a[i];
ハードウェアの「キャッシュライン」を知っていれば、これが当然と分かります。
9.1.2 OS・コンパイラ・ネットワークの基盤¶
第 10 章 (OS)、第 11 章 (ネットワーク)、第 13 章 (コンパイラ) はすべて本章の上に立ちます。
9.2 数の表現¶
9.2.1 2 進数¶
人間は 10 進、コンピュータは 2 進。
| 10 進 | 2 進 |
|---|---|
| 0 | 0000 |
| 1 | 0001 |
| 5 | 0101 |
| 10 | 1010 |
| 255 | 11111111 |
ビット・バイト・ワード¶
- 1 ビット: 0 または 1
- 1 バイト = 8 ビット (256 通り)
- 1 ワード = CPU の自然な単位 (32 ビット or 64 ビット)
9.2.2 2 の補数表現¶
負の数の表現方法。
例 (8 ビット):
- \(5\) → 00000101
- \(-5\) → 11111011 (5 のビット反転 + 1)
メリット: - 加算回路が同じで済む - 0 が 1 つだけ - 範囲: \(-2^{n-1}\) から \(2^{n-1} - 1\)
オーバーフロー¶
8 ビットで \(127 + 1 = 128\) → ビット列としては 10000000 = \(-128\)。「整数が突然負になる」のはこれ。安全な言語 (Rust, Swift) は実行時にチェックします。
9.2.3 浮動小数点 (IEEE 754)¶
\(\pm 仮数 \times 2^{指数}\) の形。32 ビット (float) と 64 ビット (double) が一般。
構造(32 ビットの場合)¶
┌─┬────────┬───────────────────────┐
│S│ Exponent│ Mantissa │
│1│ 8 bit │ 23 bit │
└─┴────────┴───────────────────────┘
符号 指数(バイアス付き) 仮数
よくある罠¶
10 進の 0.1 は 2 進では循環小数。だから誤差が出る。
金額計算では絶対に float を使わない。Decimal 型や整数(円→銭)を使う。
特殊値¶
+0,-0+Infinity,-InfinityNaN(Not a Number) —0/0,sqrt(-1)など
9.3 ブール代数と論理回路¶
9.3.1 ゲート¶
第 5 章で扱った AND・OR・NOT を、半導体で実装したのが 論理ゲート。
NAND だけ で、すべての論理回路が作れます。これが現代 CPU の物理基盤。
9.3.2 組合せ回路¶
入力だけで出力が決まる回路。
半加算器 (HA)¶
1 ビットの加算:
全加算器 (FA)¶
桁上げも含む加算。これを並べると 32 ビット加算器ができます。
ALU (Arithmetic Logic Unit)¶
加算、減算、論理演算をまとめた回路。CPU の核。
9.3.3 順序回路¶
状態を持つ回路。フリップフロップ (D-FF, JK-FF, T-FF) が基本素子。
CPU、メモリ、I/O コントローラーは順序回路の集まりです。
9.4 命令セットアーキテクチャ (ISA)¶
ハードウェアとソフトウェアの境界。
| ISA | 特徴 | 用途 |
|---|---|---|
| x86 / x86-64 | CISC、複雑、可変長 | PC, サーバ |
| ARM | RISC、固定長、省電力 | スマホ、Apple Silicon |
| RISC-V | オープン、教育・研究で人気 | 組込み、新興チップ |
9.4.1 命令の種類¶
- データ転送: load, store, mov
- 算術論理: add, sub, and, or, shift
- 制御: branch, jump, call, return
- システム: syscall, int
9.4.2 アセンブリ例 (RISC-V)¶
「x10 = 5; x11 = 3; x12 = 8」と読み替えられます。
9.4.3 アセンブリを学ぶ意味¶
- C のポインタやインライン関数の最適化を理解できる
- セキュリティ脆弱性 (バッファオーバーフロー) の仕組みが分かる
- 性能チューニング時に「コンパイラがこう吐いている」を読める
9.5 CPU の動作¶
9.5.1 5 ステージのデータパス¶
教育用の RISC-V を例に:
graph LR
IF[IF<br/>Fetch<br/>命令取得] --> ID[ID<br/>Decode<br/>命令解読]
ID --> EX[EX<br/>Execute<br/>ALU 計算]
EX --> MEM[MEM<br/>Memory<br/>load/store]
MEM --> WB[WB<br/>Write Back<br/>結果書戻]
style IF fill:#bbdefb
style ID fill:#c5e1a5
style EX fill:#ffe0b2
style MEM fill:#f8bbd0
style WB fill:#d1c4e9
- IF (Instruction Fetch): PC から命令を取得
- ID (Instruction Decode): 命令を解読、レジスタ読み出し
- EX (Execute): ALU で計算
- MEM (Memory Access): load/store ならメモリアクセス
- WB (Write Back): 結果をレジスタに書き戻す
毎サイクル、命令が 1 つ進みます。
9.5.2 制御ユニット¶
各ステージに「この命令で何をするか」を指示する司令塔。
9.6 パイプライン — 同時並行実行¶
9.6.1 アイデア¶
5 ステージを 重ねて 実行。1 サイクルに 1 命令完成 (理想時 IPC = 1)。
時刻 →
命令1: IF ID EX MEM WB
命令2: IF ID EX MEM WB
命令3: IF ID EX MEM WB
命令4: IF ID EX MEM WB
命令5: IF ID EX MEM WB
└───────── 1 サイクルに 1 命令完成 (理想時 CPI = 1) ─────────┘
「工場の流れ作業」と同じ発想。組立ラインで各工程が並行して動く。
工場の流れ作業と同じ発想。
9.6.2 ハザード — パイプラインの敵¶
構造ハザード¶
同じハードウェアの取り合い。命令メモリとデータメモリを分離して解消。
データハザード¶
「前の命令の結果を、まだ書き戻し前に次が使う」。
対策: - フォワーディング: ALU 出力を直接次の命令へ - パイプラインストール: 1 サイクル待つ
制御ハザード¶
分岐命令で「どこへ飛ぶか分からない」とフェッチが無駄に。
対策: - 分岐予測: 「過去の傾向」から予測 - 投機実行: 予測通りに進め、間違っていたら戻る
現代 CPU の分岐予測精度は 95% 以上。
9.6.3 スーパースカラと OoO 実行¶
複数の実行ユニットを並列に持ち、依存のない命令を アウトオブオーダ で実行。
「Tomasulo アルゴリズム」「レジスタリネーミング」で並列性を最大化。Intel Core i7、Apple M1 もこの仕組み。
9.6.4 Spectre / Meltdown — 投機実行が脆弱性に¶
投機実行を悪用した サイドチャネル攻撃 が 2018 年に発覚。CPU 全体の設計見直しを迫る大事件。
「最適化と安全性のトレードオフ」の典型例です。
9.7 メモリ階層 — 速度とサイズのトレードオフ¶
9.7.1 階層構造¶
| 層 | 容量 | 遅延 | 例 |
|---|---|---|---|
| レジスタ | 数 KB | 1 サイクル | CPU 内 |
| L1 キャッシュ | 数十 KB | 4 サイクル | CPU コア内 |
| L2 キャッシュ | 数百 KB | 12 サイクル | コア内 |
| L3 キャッシュ | 数十 MB | 40 サイクル | チップ内共有 |
| 主記憶 (DRAM) | GB | 100+ サイクル | DIMM |
| SSD | TB | 数万サイクル | NVMe |
| HDD | TB | 数百万サイクル | 磁気ディスク |
比喩¶
L1 = 自分の机の上、L2 = 引き出し、L3 = 本棚、DRAM = 隣の部屋、SSD = 倉庫、HDD = 別の建物。距離 = 遅延 と思えば直感的。
9.7.2 局所性 — キャッシュが効く理由¶
- 時間局所性: 一度使ったデータは再利用される
- 空間局所性: 近くのデータも使われる
// 良い: 連続アクセス、空間局所性
for (i = 0; i < N; i++) sum += a[i];
// 悪い: 飛び飛び
for (i = 0; i < N; i++) sum += a[i * 1000];
9.7.3 キャッシュ構造¶
- ライン: 64 B が一般。ライン単位で読み込み
- 直接マップ: 1 つのアドレスは 1 つの場所だけ
- セットアソシアティブ: \(n\)-way、\(n\) 個の候補
- フルアソシアティブ: どこにでも入れる
- 置換: LRU, FIFO, ランダム
9.7.4 行優先 vs 列優先 — 重要な実例¶
double a[1024][1024];
// 行優先 (C のメモリレイアウト)
for (i = 0; i < 1024; i++)
for (j = 0; j < 1024; j++) sum += a[i][j]; // 速い
// 列優先
for (j = 0; j < 1024; j++)
for (i = 0; i < 1024; i++) sum += a[i][j]; // 遅い (10 倍以上)
C/C++ のメモリレイアウトは 行優先。Fortran は列優先。配列のアクセス順を間違えると桁違いに遅くなる。
9.7.5 キャッシュコヒーレンス¶
マルチコアでは、各コアの L1 が矛盾しないようにする必要があります。MESI プロトコル (Modified, Exclusive, Shared, Invalid) で管理。
「False sharing」(同じキャッシュラインに別スレッドのデータ) は性能悪化の有名な落とし穴。
9.8 仮想記憶¶
9.8.1 概念¶
各プロセスに「自分専用の連続したメモリ空間」を提供。
プロセス A の仮想空間: 0x0 ─ 0xFFFF
プロセス B の仮想空間: 0x0 ─ 0xFFFF (別物!)
↓
↓ 物理メモリへの「翻訳」
↓
物理メモリ: [プロセス A のページ] [プロセス B のページ] ...
9.8.2 ページング¶
仮想空間を 4 KB のページ に分割し、物理フレームに対応付け。
ページテーブル¶
x86-64 は 4 段の多段ページテーブル (4 KB × 512 × 512 × 512 × 512 = 256 TB の仮想空間)。
9.8.3 TLB (Translation Lookaside Buffer)¶
ページテーブルのキャッシュ。これがないと毎メモリアクセスで 4 段のページテーブルを引く必要があり、致命的に遅くなります。
9.8.4 ページフォルト¶
物理メモリにないページにアクセス → OS が処理。
- マイナーフォルト: 物理にあるが対応がついてない (mmap など)
- メジャーフォルト: ディスクから読み込む必要あり (遅い)
9.8.5 スワップ¶
物理メモリ不足時、ページを ディスクに退避。スワップが多発すると激遅 (スラッシング)。
「メモリ不足に気付いたら、設計を見直すかメモリを増やす」のが鉄則。
9.9 I/O とバス¶
9.9.1 通信方式¶
- メモリマップド I/O: デバイスをメモリアドレスとして扱う
- ポート I/O: 専用命令 (x86 の
in/out) - 割り込み: デバイスから CPU への通知
- DMA: CPU を介さずデバイスとメモリで転送
9.9.2 バス¶
- PCIe (内部接続、グラボ・SSD)
- USB (外部接続)
- SATA (旧式 SSD/HDD)
- Thunderbolt (高速外部)
9.10 並列性の階層¶
9.10.1 4 つのレベル¶
- 命令レベル並列 (ILP): パイプライン、スーパースカラ
- データ並列: SIMD (SSE, AVX, NEON)、GPU
- スレッドレベル並列: マルチコア、SMT (Hyper-Threading)
- 要求レベル並列: クラスタ、データセンタ
9.10.2 Amdahl の法則¶
「直列部分がボトルネックになる」
\(N\) プロセッサでの最大スピードアップ: $\(S = \frac{1}{s + (1-s)/N}\)$
\(s\) が直列部分の割合。
例: \(s = 10\%\) なら、コアを無限に増やしても 最大 10 倍 しか速くならない。
9.10.3 Gustafson の法則¶
「問題サイズも増やせば天井はない」
リアルワールドでは問題サイズも増えるので、Amdahl ほど悲観的ではない。
9.11 GPU と特殊アクセラレータ¶
| 種類 | 用途 |
|---|---|
| CPU | 汎用処理 |
| GPU | 大量並列のデータ処理 |
| TPU | 行列積 (Google) |
| FPGA | 再構成可能ロジック |
| ASIC | 特定用途専用 |
機械学習の発展は GPU の進化と表裏一体。NVIDIA H100 で ChatGPT が動いています。
9.12 性能の測り方¶
9.12.1 指標¶
- スループット: 単位時間あたり処理量 (IPS, FLOPS)
- 遅延 (latency): 1 件あたりの時間
- IPC: 1 サイクルあたり命令数
9.12.2 ベンチマーク¶
- SPEC (CPU)
- MLPerf (機械学習)
- TPC (データベース)
9.12.3 プロファイリング¶
実機での性能測定:
- perf (Linux)
- VTune (Intel)
- gprof (GNU)
- Apple Instruments
「推測するな、計測せよ」が性能チューニングの鉄則。
9.12.4 ルーフラインモデル¶
「演算強度 (Flops/Byte)」でボトルネックを判定。CPU 律速 vs メモリ律速を切り分けられます。
9.13 演習問題¶
- \(-37\) を 8 ビット 2 の補数で表せ。
0.1 + 0.2 != 0.3を IEEE 754 表現で説明せよ。- NAND ゲート 4 個で XOR を構成せよ。
- RISC-V で
for (i = 0; i < 10; i++) sum += a[i];をアセンブリで書け。 - データハザードのフォワーディングが効かないケース (load-use) を例示せよ。
- キャッシュサイズ 32 KB、ライン 64 B、4-way の場合のセット数を計算せよ。
for (i; i<N; i++) for (j; j<N; j++) A[j][i]とA[i][j]のキャッシュ効率の違いを説明せよ。- Amdahl の法則で、直列 10% の作業を 100 コアで実行したときのスピードアップを求めよ。
- ページサイズ 4 KB、仮想アドレス 32 ビット、物理メモリ 1 GB のとき、単一段ページテーブルのサイズを計算せよ。
perf statを使って自作プログラムの IPC、キャッシュミス率を測れ。
9.14 この章のまとめ¶
| 階層 | 内容 |
|---|---|
| トランジスタ → 論理回路 | NAND, FF |
| 論理回路 → ALU・CPU | データパス、制御 |
| パイプライン | 並列実行、ハザード |
| キャッシュ | 局所性、ライン |
| 仮想記憶 | ページテーブル、TLB |
| 並列性 | ILP, SIMD, SMT |
| Amdahl | 直列部分が天井 |
ハードウェアの感覚を持ったエンジニアは、コードを「実機の挙動」で見抜けます。「キャッシュに乗るか」「分岐予測が当たるか」を考える だけで、性能の桁が変わります。
9.15 次に読むもの¶
- Patterson & Hennessy, Computer Organization and Design (RISC-V 版)
- Hennessy & Patterson, Computer Architecture: A Quantitative Approach
- Bryant & O'Hallaron, Computer Systems: A Programmer's Perspective (CSAPP) — 名著
- nand2tetris プロジェクト — NAND からコンピュータを作る体験
- Onur Mutlu の YouTube 講義 (CMU/ETH)
🌟 メッセージ 「CPU は魔法ではなく、論理回路の集まり」と腹落ちすると、デバッグも最適化も自信を持って進められます。1 度 nand2tetris を完走すると、世界が変わります。