CPU

概要

命令実行・パイプライン・性能の見方

CPU は、プログラムを実際に動かす中心です。このテキストでは、CPU がどう命令を読み、どう並列化し、なぜ速い遅いが起きるのかを説明します。

要点
CPU を理解すると、「コードが走る」とは何かが具体的に見えてきます。レジスタ、ISA、パイプライン、分岐予測、SIMD の直感がつくと、性能の話が急に現実的になります。

命令列そのものを具体的に読みながら進みたいときは、アセンブラ を横に置くと、命令レジスタスタック の像がかなり固まります。

この章で重視すること

  • CPU をブラックボックスではなく、段階的な実行装置として見る
  • 性能をクロック周波数だけで語らない
  • 分岐予測や SIMD のような現代 CPU の工夫を直感でつかむ
  • CPU の歴史と実装を通じて、設計の進化を知る
  • ISA と マイクロアーキテクチャの違いを深く理解する

目次

  1. CPU の歴史
  2. CPU の役割
  3. 命令実行の基本
  4. レジスタと ISA
  5. ISA 各種の詳細
  6. パイプラインと分岐予測
  7. 高度な実行技法
  8. キャッシュとメモリ階層
  9. SIMD と並列化
  10. マルチコアと並列性
  11. 浮動小数点とベクトル拡張
  12. 割り込みと例外
  13. セキュリティ拡張
  14. 仮想化拡張
  15. 電力管理
  16. 実機実例
  17. 2025-2026 動向
  18. 性能の見方
  19. 実務ケーススタディ
  20. 発展ルート
  21. 参考文献

CPU の歴史

CPU の進化を知ることで、なぜ現代 CPU がそのような設計をしているのかが見えてきます。

黎明期:真空管と初期のコンピュータ(1940-1950年代)

ENIAC(1946)

  • 30 トンの重さ、150 kW の電力消費
  • 真空管約 18,000 本で構成
  • 動作周波数:約 5 kHz
  • 加算:200 マイクロ秒
  • プログラミングは配線の付け替えで実施

この時代のコンピュータは、プログラム内蔵方式(von Neumann アーキテクチャ) を採用しました。命令とデータをメモリに置く仕組みが、現代 CPU の基礎になっています。

トランジスタ時代(1950-1960年代)

IBM System/360(1964)

  • トランジスタ採用
  • 50,000 個以上のトランジスタ
  • 複数の互換性のある製品ラインアップを展開
  • ISA(Instruction Set Architecture)という概念の実践

この時期から、ISA と実装の分離 が確立されました。複数の製品が同じ ISA を持ちながら、内部構造は異なるという設計パラダイムが生まれました。

ISA を実際の命令表記として見たいなら、アセンブラ の x86-64 / ARM64 の例が補助になります。

IC と LSI 時代(1970-1980年代)

Intel 4004(1971)

  • 2,300 個のトランジスタ
  • 動作周波数:740 kHz
  • 最初のマイクロプロセッサ

Intel 8086(1978)

  • 29,000 個のトランジスタ
  • 動作周波数:5 MHz
  • 16 ビット ISA
  • x86 系統の始祖

Intel 80386(1985)

  • 275,000 個のトランジスタ
  • 32 ビット拡張(x86-32)
  • 仮想メモリと保護機構を搭載
  • 現代 x86 の骨格を確立

パイプラインの登場(1980-1990年代)

Intel Pentium(1993)

  • 5 段パイプラインを初搭載
  • 動作周波数:60 MHz~
  • 分岐予測機構の導入
  • スーパースカラ実行(複数ユニットでの並列実行)

MIPS R3000(1988)/R10000(1996)

  • RISC 的設計を徹底
  • 後にアウト・オブ・オーダー実行を採用(R10000)
  • 動的分岐予測

IBM POWER1(1990)/POWER2(1993)

  • 高性能なスーパースカラ設計
  • RISC ベースで企業向け市場を席巻

この時期、CPU が「命令を 1 つ 1 つ処理する」から「複数命令を重ねて流す」へ進化しました。

OoO 実行と高い周波数(1990-2000年代)

Intel Pentium Pro/II/III(1995-1999)

  • 6-7 段パイプライン
  • アウト・オブ・オーダー(OoO)実行
  • マイクロオペレーション分解(複雑な x86 命令を内部で簡単な micro-ops に分解)
  • L2 キャッシュを同一チップに統合

AMD K6(1997)/Athlon(1999)

  • より深いパイプライン
  • より高い周波数で競争
  • 動作周波数:500 MHz~1 GHz

IBM POWER3(1998)

  • 8 段パイプライン
  • IBM サーバー市場での支配力強化

この時期から、IPC(命令/サイクル)が性能改善の中心 になりました。クロック周波数の伸びだけでなく、1 サイクルあたりどれだけ命令を進められるかが重要になったのです。

マルチコア時代(2000-2010年代)

Intel Pentium D/Core 2 Duo(2005-2006)

  • 初のマルチコア大衆化
  • 1 つのダイに複数コアを統合
  • コア間で L3 キャッシュ共有

AMD Opteron(2003)/Phenom(2007)/EPYC(2017)

  • NUMA(Non-Uniform Memory Access)を採用
  • サーバー/ワークステーション向けで高性能を実現

ARM Cortex シリーズ(2003-)

  • モバイルデバイス向けで指数関数的な普及
  • 低電力設計が特徴
  • big.LITTLE(Cortex-A72 + Cortex-A53)による異種マルチコア

Intel Sandy Bridge(2011)/Ivy Bridge(2012)

  • 14 段パイプライン
  • より効率的なアウト・オブ・オーダー実行
  • AVX(256-bit SIMD)搭載
  • 動作周波数:3+ GHz

現代(2015-2026年)

Apple Silicon M1(2020)/M2(2022)/M3/M4(2023-2024)

  • 統合 GPU とニューロモーフィックエンジン(Neural Engine)
  • 低電力で高性能(モバイルとデスクトップの融合)
  • Unified Memory(CPU と GPU が同じメモリ階層)
  • 動作周波数:3.2-4.5 GHz

Intel Alder Lake(2021)と P-core/E-core

  • Performance コア(Alder Lake P)と Efficiency コア(E)の分離
  • 動的に使い分ける異種マルチコア
  • これを big.LITTLE と区別

AMD EPYC Genoa(2023)

  • 12 コア × 12 CXU(Core Complex Unit)= 144 コア
  • 3D V-Cache で L3 キャッシュを 2 倍
  • 動作周波数:3.0+ GHz
  • サーバー市場での圧倒的シェア

ARM Cortex-X4/A725 など(2024-)

  • 高性能パフォーマンスコアと効率コアの組み合わせ
  • より深いパイプライン
  • 高度な分岐予測と推測実行

CPU の役割

CPU は、命令を読み、解釈し、実行します。プログラムが「動く」と言うとき、その中心にいるのが CPU です。

flowchart LR A[メモリ上の命令] --> B[CPU] B --> C[計算] C --> D[結果をレジスタ / メモリへ]

CPU の中核的な役割

  1. 命令フェッチ(Instruction Fetch)

    • メモリから次の命令を読み出す
    • プログラムカウンタ(PC)を進める
  2. 命令デコード(Instruction Decode)

    • バイナリ形式の命令を解釈
    • どの演算を、どのデータで行うか決定
  3. 命令実行(Execute)

    • ALU(Arithmetic Logic Unit)で計算
    • メモリアクセスの開始
    • 分岐先の計算
  4. メモリアクセス(Memory Access)

    • レジスタ・キャッシュ・メイン メモリへのアクセス
    • データの読み書き
  5. 結果ライトバック(Write Back)

    • 計算結果をレジスタに書き込む
    • 次の命令へ値を引き渡す

1 命令が流れるときに何が起きるか

入門では fetch -> decode -> execute と 3 語で済ませがちですが、実際には CPU は 1 命令に対してかなり多くの判断をしています。

たとえば add rax, rbx のような単純な命令でも、CPU はざっくり次を行います。

  1. 次に読む命令アドレスを決める
  2. 命令バイト列を命令キャッシュから取る
  3. 命令をデコードして「どの演算器を使うか」を決める
  4. 必要なオペランドがレジスタやキャッシュにあるか確認する
  5. 実行ユニットへ投入する
  6. 結果を一時的に保持し、最終的にレジスタへ反映する

【図2-1】1 命令の見え方と CPU 内部の流れ:

flowchart LR A["命令列"] --> B["Fetch"] B --> C["Decode"] C --> D["Rename / Schedule"] D --> E["Execute"] E --> F["Retire / Commit"] F --> G["レジスタやメモリに反映"]

ここで大事なのは、プログラマから見える順番と、CPU 内部での一時的な処理順が必ずしも同じではないことです。後で出てくる Out-of-Order 実行投機的実行 は、この「見える順番」と「内部で前倒しする順番」を分ける工夫だと思うと理解しやすくなります。

CPU が見ている「状態」

CPU が仕事を進めるときに見ている状態は、大きく 2 層あります。

  • アーキテクチャ状態: ソフトウェアから見える状態。レジスタ、PC、フラグ、メモリ
  • マイクロアーキテクチャ状態: CPU 内部だけの状態。ROB、予約ステーション、分岐予測器、物理レジスタ、キャッシュ

この区別はとても重要です。プログラマは通常、前者だけを意識すれば十分ですが、性能やセキュリティを考えると後者の存在が効いてきます。

観点 何に効くか
アーキテクチャ状態 RAX, PC, メモリ内容 正しさ、互換性
マイクロアーキテクチャ状態 分岐予測履歴、ROB、TLB 性能、投機実行、副作用

CPU の 3 つの側面

CPU は、単純に「計算する部品」では理解できません。3 つの側面を同時に持っています。

1. 計算エンジン

  • ALU(整数演算)
  • FPU(浮動小数点演算)
  • SIMD ユニット(ベクトル演算)

2. 待ち隠蔽の工夫

  • キャッシュの活用
  • プリフェッチ
  • パイプライン化
  • 分岐予測
  • アウト・オブ・オーダー実行

3. メモリマネージャ

  • ページテーブルウォーク
  • TLB(Translation Lookaside Buffer)
  • キャッシュコヒーレンシ
  • メモリ順序付け

命令実行の基本

fetch-decode-execute サイクル

典型的には、

  1. 命令を読む
  2. 解釈する
  3. 実行する

を繰り返します。これが fetch-decode-execute サイクルです。

flowchart LR A[fetch] --> B[decode] B --> C[execute] C --> D[memory] D --> E[write back]

ただし現代 CPU では、これらが 1 命令ずつ直列に終わるのではなく、複数命令で重なり合います。だからこそ、1 命令のレイテンシたくさん流したときのスループット を分けて考える必要があります。

命令レベルで見る CPU の仕事分担

CPU をより実務的に見るなら、「CPU は何に時間を使っているのか」を分けておくと便利です。

段階 主な仕事 つまずきやすい点
Fetch 命令を取る 命令キャッシュミス、分岐ミス
Decode 命令を解釈する 複雑な命令、デコード帯域不足
Execute 演算する 実行ユニット競合、依存関係
Memory データを取る キャッシュミス、TLB ミス
Write Back / Commit 結果を確定する 例外、順序保証、投機の巻き戻し

この表を頭に入れておくと、遅い と感じたときに「演算が遅いのか」「命令が届いていないのか」「メモリ待ちなのか」を切り分けやすくなります。

現代 CPU はもっと複雑

入門では「読み、解釈し、実行する」の 3 段階で説明されますが、現代 CPU はもっと複雑です。

  • 命令を先読みする
  • 複数命令を同時に流す
  • 依存のない部分は順不同で先に進める
  • 分岐先も予測して動く
  • 命令をより小さい micro-ops に分解する(x86 など複雑な ISA の場合)

つまり CPU は、単純な逐次実行機械というより、なるべく待たずに仕事を前倒しする装置 です。

CPU は「計算機」でもあり「交通整理役」でもある

整数計算や浮動小数点計算だけが CPU の仕事ではありません。

  • どの命令が次に実行可能か
  • どのデータがまだ来ていないか
  • どの結果をどこへ書き戻すか
  • 分岐予測が外れたときの巻き戻し
  • キャッシュミスの際のスタール

といった調停も大量に行っています。速い CPU を理解するには、「演算器が速い」だけでなく、待ちをうまく隠す工夫 を見る必要があります。

簡単なアセンブリ例

x86-64 の例

; 整数加算
mov rax, 10        ; rax = 10
mov rbx, 20        ; rbx = 20
add rax, rbx       ; rax = rax + rbx = 30
mov rcx, rax       ; rcx = 30

; メモリアクセス
mov rax, [rsp]     ; スタックから読み込み
mov [rbp - 8], rax ; rbp-8 へ書き込み

; 条件分岐
cmp rax, 0
jne skip_block      ; rax が 0 でない場合スキップ
mov rax, 1
skip_block:

ARM64 の例

; 整数加算
mov x0, #10       ; x0 = 10
mov x1, #20       ; x1 = 20
add x0, x0, x1    ; x0 = x0 + x1 = 30
mov x2, x0        ; x2 = 30

; メモリアクセス
ldr x0, [sp]      ; スタックから読み込み
str x0, [bp, #-8] ; bp-8 へ書き込み

; 条件分岐
cmp x0, #0
b.ne skip_block   ; x0 が 0 でない場合スキップ
mov x0, #1
skip_block:

RISC-V の例

; 整数加算
addi x10, x0, 10    ; x10 = 10
addi x11, x0, 20    ; x11 = 20
add x10, x10, x11   ; x10 = x10 + x11 = 30
addi x12, x10, 0    ; x12 = 30

; メモリアクセス
lw x10, 0(sp)       ; スタックから読み込み
sw x10, -8(fp)      ; fp-8 へ書き込み

; 条件分岐
beq x10, x0, skip   ; x10 が 0 の場合スキップ
addi x10, x0, 1
skip:

レジスタと ISA

レジスタ

CPU 内にある非常に高速で小さな記憶領域です。

  • 計算中の値
  • アドレス
  • 制御情報

を持ちます。

レジスタは CPU にとっての「机の上」です。机の上にあるものはすぐ触れますが、数は限られます。メモリに置いた値は広く持てる代わりに遠いので、コンパイラや CPU はなるべくレジスタへ置きたがります。

この直感を命令単位で見たいときは、アセンブラレジスタメモリアクセス のコード例がつながります。

レジスタの種類

汎用レジスタ(General-Purpose Registers)

x86-64

  • RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP
  • R8-R15
  • 全 16 個(各 64 ビット)

ARM64

  • X0-X30 (31 個の汎用レジスタ)
  • X31 はスタックポインタまたはゼロレジスタ
  • 各 64 ビット

RISC-V

  • X0-X31 (32 個)
  • X0 はハードウェアゼロ
  • X1 はリターンアドレス
  • 各 64 ビット(RV64I)

特殊レジスタ

x86-64

  • RIP:命令ポインタ(次に実行する命令のアドレス)
  • RFLAGS:フラグレジスタ(キャリー、ゼロ、オーバーフロー、符号等)
  • CR0-CR4:制御レジスタ(ページング有効化、保護モード等)
  • MSR(Model-Specific Register):CPU 固有の設定

ARM64

  • PC:プログラムカウンタ
  • SP:スタックポインタ
  • LR:リンクレジスタ(関数呼び出しの戻り先)
  • NZCV:コンディションフラグ
  • ELRELxELR_ELx:例外からの戻り先

RISC-V

  • PC:プログラムカウンタ
  • SP(X2):スタックポインタ
  • RA(X1):戻りアドレス
  • MSTATUS:マシン状態レジスタ

SIMD レジスタ

x86-64

  • XMM0-XMM15:128 ビット(SSE)
  • YMM0-YMM15:256 ビット(AVX)
  • ZMM0-ZMM31:512 ビット(AVX-512)

ARM64

  • V0-V31:128 ビット(NEON)
  • SVE Z0-Z31:可変長(最大 2048 ビット、ARM SVE)

RISC-V

  • V0-V31:可変長(RVV)
  • VLEN:ベクトルレジスタの長さ

ISA(Instruction Set Architecture)

ISA(Instruction Set Architecture)は、CPU が理解する命令の集合です。

  • x86-64
  • ARM64
  • RISC-V
  • MIPS(衰退中)
  • POWER(企業向け)

同じ C プログラムでも、最終的な機械語は ISA に応じて変わります。

ISA とマイクロアーキテクチャの違い

ここはとても大事です。

  • ISA: 外から見える約束。どんな命令があり、どう振る舞うか。
  • マイクロアーキテクチャ: その約束を内部でどう実装するか。

たとえば同じ x86-64 でも、CPU ごとにパイプラインの深さ、キャッシュ構成、分岐予測器、実行ユニット数は違います。つまり「同じ機械語が動く」ことと「同じ速さで動く」ことは別です。

例:Intel Core i9 と AMD Ryzen 9 の比較(同じ x86-64)

項目 Intel Core i9-14900K AMD Ryzen 9 7950X
ISA x86-64 x86-64
P-core パイプライン 14 段 12-13 段
L3 キャッシュ/core 2.5 MB 6 MB(3D V-Cache 有)
分岐予測 Indirect Branch Prediction(IBP) ???
動作周波数 3.2-6.2 GHz 4.5-5.7 GHz
マルチスレッド性能 性能スレッド優先 バランス型

同じ ISA でも、マイクロアーキテクチャが違うため、同じプログラムの実行時間は大きく異なることがあります。


ISA 各種の詳細

x86-64

起源

  • Intel 8086(1978)の 16 ビット ISA から発展
  • AMD が 64 ビット拡張を提案(2003)
  • AMD64 / Intel 64 として標準化

特徴

  • 複雑な命令セット(CISC):個々の命令が複雑な処理を実行
    • mov rax, [rbx + rcx * 8 + 16] のような複雑なメモリアドレッシング
    • 可変長命令(1-15 バイト)
  • 可変レジスタ数(時代により拡張):元々は 8 個の汎用レジスタ(8086)→ 16 個(x86-64)
  • 後方互換性:すべての x86-64 CPU は 16 ビット x86 機械語も実行可能
  • マイクロオペレーション分解:複雑な命令を内部でより小さい操作(μ-ops)に分解してパイプライン実行

主な拡張

  • SSE(Streaming SIMD Extensions):128 ビット浮動小数点
  • SSE2-SSE4.2:整数 SIMD も対応
  • AVX(Advanced Vector Extensions):256 ビット
  • AVX-512:512 ビット(Intel Xeon, Core-X シリーズのみ)

実装例

  • Intel Pentium 4, Core 2, Core i series, Xeon
  • AMD Athlon 64, Ryzen, EPYC

ARM64(ARMv8-A)

起源

  • ARM Holdings が設計(Acorn Computers 出身)
  • 当初は 32 ビット ARM ISA
  • ARMv8(2013)で 64 ビット拡張

特徴

  • シンプル設計(RISC の系統):各命令は限定的で、パイプライン効率が高い
  • 固定長命令(32 ビット):デコードが簡単
  • 豊富なレジスタ(31 個の汎用 64 ビットレジスタ)
  • 分割可能なメモリアドレッシング:複雑なメモリアドレッシング計算を事前に別の命令で行う
  • 条件付き実行が限定的:フラグによる条件分岐(条件付き実行命令は少ない)

主な拡張

  • NEON:128 ビット SIMD(ARM32 から存在)
  • SVE(Scalable Vector Extension):可変長 SIMD(128~2048 ビット)
  • SVE2:整数 SIMD も対応

実装例

  • Apple M シリーズ(統合 GPU + Neural Engine)
  • Qualcomm Snapdragon
  • Samsung Exynos
  • AWS Graviton
  • Ampere Altra(サーバー向け)

RISC-V

起源

  • UC Berkeley が設計(2010 年代)
  • オープン ISA として公開(商用化も進行中)
  • 業界から独立した標準化機構

特徴

  • 最小限の基本命令セット(RV32I, RV64I):必須命令が少ない
  • 拡張可能な設計:機能を拡張として追加
  • 固定長基本命令(32 ビット):ただし圧縮拡張(Zca)で 16 ビット命令も可能
  • シンプルなメモリアドレッシング
  • 開放性:誰でも独自の RISC-V 実装を設計・製造可能

標準拡張

  • M:乗算・除算
  • A:アトミック操作
  • F/D:単精度/倍精度浮動小数点
  • C:16 ビット圧縮命令
  • V:ベクトル(RVV)
  • K:暗号化拡張

実装例

  • SiFive Freedom U(開発ボード)
  • Alibaba XuanTie シリーズ
  • RISCV International の参照実装
  • Western Digital(SSD コントローラ)

MIPS(衰退中)

起源

  • Stanford University/ MIPS Computer Systems(1980 年代)
  • RISC の代表的な実装

特徴

  • シンプルな RISC 設計
  • 32 個の汎用レジスタ

歴史的役割

  • 1990 年代から 2000 年代に高性能サーバー/ワークステーションで主流
  • モバイルと x86 の台頭により縮小
  • 現在はルータや埋め込み機器に限定

POWER

起源

  • IBM が設計(POWER1 以降)
  • PowerPC(Motorola/IBM)から発展

特徴

  • 高性能 RISC 設計
  • 企業向けサーバー、スーパーコンピュータ向け
  • 浮動小数点が強い

実装例

  • IBM POWER9, POWER10
  • Summit スーパーコンピュータ(POWER9)

パイプラインと分岐予測

現代 CPU は 1 命令を複数段階へ分け、異なる命令を重ねて進めます。これがパイプラインです。

パイプラインは「1 命令を速く終わらせる」より、「たくさんの命令を流し続ける」ための工夫です。洗濯、乾燥、たたみを別の工程で並行に進める流れ作業を思うと近いです。

このとき流れている具体的な命令の姿は、アセンブラ具体的なコード例 を見るとイメージしやすくなります。

flowchart TD A[命令1: F] --> B[命令1: D] B --> C[命令1: E] C --> D[命令1: M] D --> E[命令1: W] F[命令2: F] --> G[命令2: D] G --> H[命令2: E] H --> I[命令2: M] J[命令3: F] --> K[命令3: D] K --> L[命令3: E] M[命令4: F] --> N[命令4: D] P[命令5: F]

各段階を F-D-E-M-W(Fetch-Decode-Execute-Memory-WriteBack)と呼びます。

従来の 5 段パイプライン(Intel Pentium 等)

  1. Fetch:命令メモリから命令を読み込み
  2. Decode:命令を解釈し、制御信号を生成
  3. Execute:ALU で計算、アドレス計算
  4. Memory:キャッシュ/メモリアクセス
  5. WriteBack:結果をレジスタに書き込み

より深いパイプライン(Intel Core 等)

クロック周波数を上げるために、パイプラインをさらに細分化します。

  • Intel Sandy Bridge:14 段
  • Intel Ivy Bridge:14 段
  • Intel Haswell:14-18 段(要素による)

パイプラインが深いほど、各段階は簡単になりますが、依存関係の解決やプリフェッチに複雑さが増します。

パイプラインハザード(段階間の依存性問題)

パイプラインが効率よく動かない場面があります。

構造的ハザード(Structural Hazard)

同じリソースに 2 つの命令が同時にアクセスしようとする場合。

命令1: ALU で計算
命令2: 同じサイクルに同じ ALU を使いたい → ストール

対策:複数の ALU を持つ(スーパースカラ設計)

データハザード(Data Hazard)

ある命令の結果が、次の命令の入力として必要な場合。

命令1: R1 = R2 + R3     (WriteBack で完了)
命令2: R4 = R1 * R5     (すぐに R1 が必要)

RAW(Read-After-Write)ハザードが代表的。

対策:

  • フォワーディング(Forwarding / Bypass):WriteBack を待たず、計算結果を直接次の命令へ渡す
  • レジスタリネーミング:物理レジスタを動的に割り当てる

制御ハザード(Control Hazard)

分岐命令で次に実行すべき命令が確定しない場合。

条件分岐命令
その後ろの命令たち(ブランチの結果は未確定)

対策:分岐予測(後述)

パイプラインはどう詰まるのか

パイプラインは水道管のようなものです。理想的には毎サイクル新しい命令を流し込みたいのですが、途中で詰まると全体の流れが悪くなります。

【図7-1】理想的なパイプラインとストール:

flowchart TD A["命令1"] --> A1["F"] --> A2["D"] --> A3["E"] --> A4["M"] --> A5["W"] B["命令2"] --> B1["F"] --> B2["D"] --> B3["stall"] --> B4["E"] --> B5["M"] --> B6["W"] C["命令3"] --> C1["F"] --> C2["stall"] --> C3["D"] --> C4["E"] --> C5["M"] --> C6["W"]

ストールが痛いのは、遅れた 1 命令だけでなく、その後ろの命令列まで巻き添えになりやすいからです。分岐予測ミスや L1 ミスが体感性能に効くのは、この「後続の流れも止める」性質があるからです。

フォワーディングがないと何が起きるか

データハザードは、教科書で読むだけだと抽象的です。次の 2 命令を考えます。

add r1, r2, r3
mul r4, r1, r5

2 行目は 1 行目の r1 をすぐ使いたいのですが、1 行目の結果がまだレジスタへ正式に書き戻されていないかもしれません。ここでフォワーディングがないと、2 行目は待つしかありません。

flowchart LR A["add の結果"] --> B["フォワーディングで直接渡す"] B --> C["mul がすぐ使える"]

この「書き戻し完了を待たず、途中結果を横流しする」工夫が、現代 CPU のスループットをかなり支えています。

分岐予測

条件分岐が来るたびに完全停止すると遅すぎるので、CPU は「たぶんこう進むだろう」と予測します。

  • 当たれば速い
  • 外れるとパイプラインが無駄になる

静的分岐予測

コンパイルタイムに予測方針を固定。

; バックエッジ(ループのジャンプ)は「取る」と予測
; 前方分岐は「取らない」と予測(例外処理など)
loop:
  dec rcx
  jnz loop      ; 通常は「取る」と予測
  ...

利点:ハードウェア簡単 欠点:実行パターンに対応できない

動的分岐予測

実行時の分岐パターンを学習。

2-bit 飽和カウンタ

最も簡単な動的予測。各分岐命令につき、「取った」「取らない」のカウンタを保持。

状態遷移:
強く取る(11) → 予測「取る」
弱く取る(10) → 予測「取る」
弱く取らない(01) → 予測「取らない」
強く取らない(00) → 予測「取らない」

分岐が実際に「取った」なら、カウンタを増加方向へ
分岐が「取らない」なら、減少方向へ

パターン履歴テーブル(PHT: Pattern History Table)

過去 N 命令分の分岐結果をパターンとして記録。

PC = 0x1000 の分岐について、過去 8 命令の結果が:
TNTTNTNTN  (T=Take, N=Not taken)
を見たことがあるなら、次は T と予測

利点:より複雑なパターンに対応 欠点:履歴テーブルのサイズがメモリになる

TAGE 予測器(Tagged Geometric History)

複数の履歴長を持つテーブルを併用。短い履歴(最新の分岐)と長い履歴(周期的パターン)両方を見る。

  • Intel Skylake 以降、多くの高性能 CPU が採用
  • 予測精度 95-98%

Perceptron ベースの予測器

機械学習(パーセプトロン)で分岐パターンを学習。

  • 学術的関心が高い
  • 一部の研究実装で採用

予測精度の現状

  • 簡単なプログラム:95% 以上
  • 不規則なパターン(フェーズ変化、チェーンハッシング):85-90%
  • 大量の分岐(特に間接分岐):80-85%

投機的実行(Speculative Execution)

予測に基づいて先回りして実行する仕組みです。

cmp rax, 0
jne label           ; 予測:「取る」(ほぼ確実)
mov rcx, 100
... (この部分も先読み実行)
label:
mov rcx, 200

利点:予測が当たれば、パイプラインが効率よく満杯 欠点:予測外れで処理を捨てて巻き戻し(パイプラインフラッシュ)

投機的実行とセキュリティ(Meltdown/Spectre)

投機的実行の結果は、メモリ階層(キャッシュ)に痕跡を残します。予測が外れても、計算途中でメモリをプリフェッチしていれば、その痕跡がキャッシュに残り、タイミング攻撃で検出可能。

この問題は、CPU セキュリティ拡張の節で詳述します。


高度な実行技法

Out-of-Order 実行(アウト・オブ・オーダー実行)

依存関係がない命令は、見た目の順番どおりでなくても先に進められます。これが Out-of-Order 実行です。

ここで CPU は「プログラム順」と「実際の内部実行順」を分けて考えます。ただし最終的な見え方はプログラムどおりに整えます。かなり器用ですが、そのぶんハードウェアは複雑になります。

実行例

int a = b + c;     // 命令1:b+c を計算 → r1
int d = e * f;     // 命令2:e*f を計算 → r2
int g = a + d;     // 命令3:r1 + r2 を計算

プログラム順:1 → 2 → 3

実際の実行順(Out-of-Order):1 と 2 は並列実行(依存なし) → 3 は両方の完了を待つ

CPU は、命令1と2がレジスタの依存がないことを検出し、両方の ALU で同時に計算させます。

実装:リオーダバッファ(Reorder Buffer: ROB)

実行順序をプログラム順に戻すための重要な構造。

  1. 命令がフェッチされると、ROB に予約(enqueue)
  2. オペランドが利用可能になると、実行ユニットで実行
  3. 実行は順序を無視(OoO)
  4. 完了したら、ROB に結果を記録
  5. ROB から命令ポインタ順に「コミット」(プログラム順に見える状態にする)
  6. コミット時に、例外チェック、メモリ順序付けを行う

レジスタリネーミング

物理レジスタの数が論理レジスタより多い場合、レジスタ依存を減らせます。

:x86-64 は 16 個の論理汎用レジスタですが、内部に 200+ 個の物理レジスタを持つ(CPU 世代により異なる)。

mov r1, 10      ; 物理レジスタ P1 を r1 に割り当て
mov r1, 20      ; 別の物理レジスタ P2 を r1 に割り当て(WAW ハザード排除)
add r2, r1      ; P2 の結果を r2 へ

1 つ目の mov での r1 は P1、2 つ目は P2 に割り当てられるため、Write-After-Write ハザードが生じません。

OoO 実行のコスト

  • ROB、物理レジスタ、命令ウィンドウが大きくなる
  • ハードウェア複雑さ増加
  • 消費電力増加
  • ただし IPC 向上で性能大幅改善

スーパースカラ実行(Superscalar)

複数の実行ユニットで、複数の命令を同時実行。

例:Intel Core 2 Duo

  • 4 個の ALU
  • 2 個の浮動小数点ユニット
  • 1 個のメモリアクセスユニット → 理想的に 1 サイクルで 4 命令進行可能(整数のみ)

マイクロオペレーション(Micro-ops)分解

x86-64 のような複雑な CISC ISA を、内部ではより簡単な RISC 的な「マイクロオペレーション」(μ-ops)に分解。

; 複雑な x86 命令
add rax, [rbx + rcx * 8 + 16]

; 内部で分解
μ-op1: rax1 = rbx + 16                ; アドレス計算の一部
μ-op2: rax2 = rcx * 8                  ; スケーリング計算
μ-op3: rax3 = rax1 + rax2              ; アドレス完成
μ-op4: load_result = メモリ[rax3]      ; メモリロード
μ-op5: rax = rax + load_result         ; 最終的な加算

1 つの x86 命令が 5 つの μ-ops に分解される例。Intel Sandy Bridge 以降は、微細な最適化により、多くの命令は 1-2 μ-ops で実行される。


キャッシュとメモリ階層

CPU 単体で速くても、必要なデータが来なければ待つしかありません。だから CPU の話は自然にキャッシュやメモリ階層へつながります。

キャッシュの階層構造

flowchart TD A[レジスタ 100+ 個、64 ビット] B[L1 命令キャッシュ 32-64 KB] C[L1 データキャッシュ 32-64 KB] D[L2 キャッシュ 256 KB - 1 MB] E[L3 キャッシュ 2-12 MB] F[メイン メモリ 8-32+ GB] A --> D B --> D C --> D D --> E E --> F

L1 キャッシュ

特性

  • 最小(32-64 KB 程度)
  • 最速(3-4 サイクルで応答)
  • コア専用(マルチコアでは各コアが独立)
  • 命令キャッシュとデータキャッシュに分離

セット連想度(Set Associativity)

  • 8-way set associative が一般的
  • ブロックサイズ:64 バイト

L2 キャッシュ

特性

  • 中程度(256 KB~1 MB)
  • 中速(12-15 サイクル)
  • コア専用(L1 ミス時に最初に見るキャッシュ)
  • 統一キャッシュ(命令 + データ)

L3 キャッシュ

特性

  • 大きい(2-12+ MB)
  • 遅い(40-50 サイクル)
  • マルチコア共有(L2 ミス時に見る)
  • 統一キャッシュ

3D V-Cache(AMD Ryzen 5000/7000 シリーズ)

  • L3 キャッシュを 3D で垂直に積層
  • 従来の 2 倍容量
  • 同じ遅延で 2 倍の有効容量

キャッシュコヒーレンシプロトコル

マルチコア環境では、複数のコアが同じデータを持つ可能性があります。どのコピーが「最新」かを管理する必要があります。

MESI プロトコル

4 つの状態を持つ:

  1. Modified(修正済み):このコアだけが有効なコピーを持ち、メモリと異なる
  2. Exclusive(独占的):このコアだけが有効なコピーを持ち、メモリと同じ
  3. Shared(共有):複数のコアが同じ値のコピーを持つ
  4. Invalid(無効):このコアのコピーは無効(他のコアが修正した)

MOESI プロトコル

MESI に Owned 状態を追加。AMD EPYC など一部で採用。

Directory ベースコヒーレンシ

大規模なマルチコアシステムでは、全コアのコピーを追跡するメモリディレクトリを持つ。

キャッシュミスの種類

  1. コンパルサリミス(Compulsory Miss):初めてアクセスするデータ(避けられない)
  2. 容量ミス(Capacity Miss):キャッシュが満杯で、古いデータが廃棄された
  3. 競合ミス(Conflict Miss):複数のアドレスが同じキャッシュラインにマップされる

遅延の桁感を持つ

CPU の話でつまずきやすいのは、どれがどれくらい遅いのか の感覚がないことです。正確なサイクル数は世代で変わりますが、ざっくりした桁感を持つだけでもかなり理解しやすくなります。

典型的な遅延感覚 何が起きるか
レジスタ ほぼ即時 すぐ演算へ渡せる
L1 数サイクル 通常はここで済ませたい
L2 十数サイクル 少し待つがまだ許容しやすい
L3 数十サイクル 目に見えて重くなる
DRAM 数百サイクル CPU から見るとかなり遠い

【図9-1】CPU から見たメモリ階層の距離感:

flowchart TD A["レジスタ"] --> B["L1"] B --> C["L2"] C --> D["L3"] D --> E["DRAM"] E --> F["ストレージ"]

この差が大きいので、CPU は「速く計算すること」以上に「遅いデータ待ちを隠すこと」に多くの工夫を割いています。

キャッシュミス時に CPU では何が起きるか

キャッシュミスは「遅い」だけで済ませると理解しにくいので、1 本のロード命令がどう苦しくなるかを流れで見ると分かりやすいです。

  1. ロード命令を発行する
  2. L1 を見る
  3. ないので L2、L3、DRAM を順に探す
  4. その間、その値に依存する命令は待つ
  5. CPU は別に進められる命令があれば OoO で前倒しする
  6. 前倒しできる仕事も尽きると、本格的に止まったように見える

つまり、キャッシュミスは単なる 1 回の待ちではなく、命令列全体の並び方に影響する待ち です。

TLB(Translation Lookaside Buffer)

仮想アドレスから物理アドレスへの変換を高速化するキャッシュ。

  • L1 TLB:最小、最速(通常、命令と データで分離)
  • L2 TLB:より大きい、少し遅い(統一)
  • ページテーブルウォーク:TLB ミス時に複数メモリ読み込みが必要(遅い)

TLB ミスの代償

  • 仮想メモリが有効な場合、TLB ミスは数百サイクルの遅延
  • 大規模なメモリワーキングセットを持つアプリケーション(データベース、仮想化)では TLB ミスが性能律速

キャッシュプリフェッチ

CPU は未来のメモリアクセスを予想し、事前にデータをキャッシュに読み込む。

ハードウェアプリフェッチ:CPU が自動的に実行

  • シーケンシャルパターン:N→N+1→N+2… を検出
  • ストライドパターン:N→N+stride→N+2*stride… を検出

ソフトウェアプリフェッチ:プログラマが明示的に指示

prefetch [rax + 64]  ; 次のイテレーション分のデータを先読み

SIMD と並列化

SIMD は 1 命令で複数データを処理する仕組みです。

  • 画像処理
  • 行列演算
  • 圧縮
  • 暗号化

で非常に重要です。

NumPy や機械学習の計算が速い背景の一つがここにあります。

スカラーとベクトルの違い

  • スカラー演算: 1 回に 1 個ずつ処理する
  • ベクトル演算 (SIMD): 同じ操作を複数データへまとめて適用する
// スカラー実装
for (int i = 0; i < N; i++) {
    c[i] = a[i] + b[i];
}

// SIMD 実装(例:AVX-256)
for (int i = 0; i < N; i += 4) {
    // 4 個の float を同時に加算
    __m256 va = _mm256_loadu_ps(&a[i]);
    __m256 vb = _mm256_loadu_ps(&b[i]);
    __m256 vc = _mm256_add_ps(va, vb);
    _mm256_storeu_ps(&c[i], vc);
}

この違いが効くのは、「たくさんのデータに同じ処理をする」場面です。

SIMD が効きやすい処理

  • 画素ごとの色変換
  • 配列どうしの加算
  • 行列演算
  • フィルタ処理
  • 暗号や圧縮の一部

逆に、分岐だらけで要素ごとに挙動が違う処理では効きにくいことがあります。

SIMD とスレッド並列は別

ここも混乱しやすいです。

  • SIMD: 1 コアの中で同じ命令を複数データへ広げる
  • スレッド並列: 複数の実行主体で仕事を分ける

どちらも「並列化」ですが、粒度も得意分野も違います。

【図10-1】SIMD とスレッド並列の違い:

flowchart TD A["1 コア"] --> B["SIMD: 1 命令で 4/8/16 要素を処理"] C["複数コア"] --> D["スレッド並列: 仕事のかたまりを分担"]
観点 SIMD スレッド並列
並列化の場所 1 コア内部 複数コア / 複数スレッド
得意な処理 同じ演算の繰り返し 独立タスクの分割
苦手な処理 分岐だらけ、要素ごとに挙動が違う 共有資源競合、ロック待ち
典型例 行列、画像、音声、暗号 Web サーバ、ジョブ実行、並列ビルド

x86-64 SIMD 系統

SSE(Streaming SIMD Extensions)

  • 128 ビットレジスタ(4 個の 32 ビット float または 2 個の 64 ビット double)
  • 加算、乗算、比較など基本演算

SSE2-SSE4.2

  • SSE2:128 ビット整数演算
  • SSE3-SSE4.2:より多くの命令セット

AVX(Advanced Vector Extensions)

  • 256 ビットレジスタ(8 個の 32 ビット float または 4 個の 64 ビット double)
  • Intel Sandy Bridge(2011)以降
  • より高いスループット

AVX-512

  • 512 ビットレジスタ
  • Intel Xeon(Skylake-SP 以降)
  • Core-X(i7-7740X 等)のみ対応
  • マスキング機能(条件付き演算)

ARM64 SIMD:NEON

  • 128 ビットレジスタ(V0-V31)
  • 整数と浮動小数点両方に対応
  • SIMD 演算をスマートフォン、タブレット上で活用(画像処理、動画デコード等)

ARM64 ベクトル拡張:SVE(Scalable Vector Extension)

  • 可変長ベクトル(128~2048 ビット)
  • ハードウェア実装により長さが決定される
  • マスキング機能
  • スーパーコンピュータ向け(Fugaku)

RISC-V ベクトル拡張:RVV(RISC-V Vector Extension)

  • 可変長(VLEN:実装により 128~複数 KB)
  • シンプルで拡張性が高い設計
  • 産業用スーパーコンピュータで採用予定

SIMD 命令例

x86-64 AVX

; 256-bit の 8 個 float(単精度)を同時に加算
vmovups ymm0, [rax]       ; メモリから 8 個の float を読み込み
vmovups ymm1, [rbx]       ; メモリから 8 個の float を読み込み
vaddps ymm2, ymm0, ymm1   ; 8 個同時に加算
vmovups [rcx], ymm2       ; 結果をメモリに書き込み

ARM64 NEON

; 128-bit の 4 個 float を同時に加算
ld1 {v0.4s}, [x0]        ; メモリから 4 個の float を読み込み
ld1 {v1.4s}, [x1]        ; メモリから 4 個の float を読み込み
fadd v2.4s, v0.4s, v1.4s ; 4 個同時に加算
st1 {v2.4s}, [x2]        ; 結果をメモリに書き込み

マルチコアと並列性

マルチコアの基本概念

現代 CPU は、複数のコア(独立した実行ユニット)を持つ。

flowchart TB D["ダイ (1 つの CPU チップ)"] C1["コア 1"] C2["コア 2"] CX["..."] L3["L3 キャッシュ (共有)"] C1I["L1i キャッシュ"] C1D["L1d キャッシュ"] C1E["実行ユニット"] C2I["L1i キャッシュ"] C2D["L1d キャッシュ"] C2E["実行ユニット"] D --> C1 D --> C2 D --> CX D --> L3 C1 --> C1I C1 --> C1D C1 --> C1E C2 --> C2I C2 --> C2D C2 --> C2E

SMP(対称型マルチプロセッシング)

すべてのコアが等しい権限と機能を持つ。メモリアクセス遅延がほぼ同じ(UMA:Uniform Memory Access)。

  • Intel Core i シリーズ(通常)
  • AMD Ryzen 5000(非 3D V-Cache 版)

NUMA(非対称メモリアクセス)

メモリが複数のノードに分散し、ローカルなメモリアクセスは速いが、リモートアクセスは遅い。

flowchart LR N1["CPU ノード 1"] --> N1C["コア 1-4"] N1 --> N1L3["L3 キャッシュ"] N1 --> N1R["ローカル RAM 20 ns 遅延"] N2["CPU ノード 2"] --> N2C["コア 5-8"] N2 --> N2L3["L3 キャッシュ"] N2 --> N2R["ローカル RAM 20 ns 遅延"] N1R <-->|"リモート RAM アクセス 200+ ns 遅延"| N2R
  • AMD EPYC(複数の 12 コア CXU を持つ)
  • IBM POWER(複数のプロセッサメモリグループ)

SMT(Simultaneous MultiThreading)/ HyperThreading

1 つのコア内で複数のスレッド(論理プロセッサ)を交互に実行。

flowchart TB P["物理コア 1"] --> T1["論理スレッド 1a"] P --> T2["論理スレッド 1b"]

1 つのコアのリソース(ALU、キャッシュ)を複数スレッドで共有。

利点:未使用のリソースを他のスレッドで活用 欠点:スレッド間のリソース競合で性能低下の可能性

HyperThreading(Intel)

  • Pentium 4 で初搭載
  • 現在のほぼすべての Intel CPU で標準

SMT(AMD)

  • Ryzen 3000 シリーズからデフォルト有効
  • 以前はオプション

異種マルチコア(Heterogeneous Multicore)

big.LITTLE(ARM)

  • 高性能コア(Cortex-A72 等)
  • 低電力コア(Cortex-A53 等)
  • 同じ ISA ですが実装が異なる

P-core と E-core(Intel Alder Lake 以降)

  • Performance core(8 個):高周波、高 IPC
  • Efficiency core(8 個):低周波、低電力
  • スケジューラが仕事の性質に応じて振り分け

キャッシュコヒーレンシの管理

マルチコアでは複雑な MESI/MOESI プロトコルが動く。

コア 1 が r1 を修正
  → L1d cache を M(Modified)へ
  → コア 2 の L1d の r1 コピーを I(Invalid)へ
  → 必要に応じてメモリに書き込み

コア 2 が r1 を読む
  → L1d cache ミス
  → コア 1 の L1d から最新値を取得
  → または メモリから取得

このトラフィックがメモリバスを圧迫することもあります。

メモリ順序付け(Memory Ordering)

Coherence(コヒーレンシ):同じアドレスへのアクセスの順序 Consistency(一貫性):複数アドレスへのアクセスの見え方

Sequential Consistency(逐次一貫性):すべての CPU から見て、メモリ操作が全 CPU で同じ順序に見える

Relaxed Consistency:より緩い順序付けで性能向上(複数 CPU の場合)

  • x86-64:比較的強い一貫性(ほぼ Sequential Consistency)
  • ARM64:比較的弱い一貫性(Performance の向上)
  • RISC-V:実装依存

マルチコアプログラミングの課題

共有メモリの競合(Race Condition)

// スレッド 1
counter = counter + 1;

// スレッド 2
counter = counter + 1;

// 期待値:counter = 2
// 実際:counter = 1(レースコンディション)

対策:ロック、アトミック操作、メモリフェンス

False Sharing

同じキャッシュラインに異なるスレッドのデータが載ると、キャッシュトラフィックが増加。

flowchart TB CL["キャッシュライン (64 バイト)"] C1["スレッド 1 の counter_1 (8 バイト)"] C2["スレッド 2 の counter_2 (8 バイト)"] W1["スレッド 1 が counter_1 を修正"] W2["キャッシュライン全体が共有状態に"] W3["スレッド 2 がメモリを再フェッチ (無駄)"] CL --> C1 CL --> C2 C1 --> W1 --> W2 --> W3

対策:パディング、スレッドローカルストレージ


浮動小数点とベクトル拡張

IEEE 754 浮動小数点

単精度(32 ビット)

[S: 1 ビット] [E: 8 ビット] [M: 23 ビット]
S: 符号
E: 指数(バイアス 127)
M: 仮数

値 = (-1)^S × 1.M × 2^(E-127)

倍精度(64 ビット)

[S: 1 ビット] [E: 11 ビット] [M: 52 ビット]

値 = (-1)^S × 1.M × 2^(E-1023)

四倍精度(128 ビット)

[S: 1 ビット] [E: 15 ビット] [M: 112 ビット]

ソフトウェア実装が主(ハードウェアサポートは限定的)

FPU(Floating-Point Unit)

x86-64:x87 FPU

  • 80 ビット内部レジスタ
  • スタック型(ST0-ST7)
  • レガシー(モダン CPU では SSE/AVX で浮動小数点)

x86-64:SSE/AVX FPU

  • XMM/YMM レジスタ
  • より高いスループット
  • パイプライン化可能

ARM64 FPU

  • V レジスタ(128 ビット NEON)
  • SVE(可変長)

ベクトル拡張の詳細

Intel AVX-512

512 ビットレジスタ:ZMM0-ZMM31

vmovaps zmm0, [rax]              ; 512 ビット(16 個の 32 ビット float)をロード
vmovaps zmm1, [rbx]
vaddps zmm2, zmm0, zmm1          ; 16 個の float を同時に加算
vmovaps [rcx], zmm2              ; 結果をストア

マスキング機能

; zmm0 と zmm1 の加算、ただしマスク k1 が 1 のレーンのみ有効
vaddps zmm2{k1}, zmm0, zmm1

; マスク内容(例)
k1 = 0xAA (1010 1010)  ; 奇数番目のレーンのみ有効

ARM64 SVE

可変長ベクトル(128~2048 ビット)

ld1w { z0.s }, p0/z, [x0]       ; p0 マスク下で 32 ビット整数をロード
ld1w { z1.s }, p0/z, [x1]
add z2.s, p0/m, z0.s, z1.s      ; ベクトル加算(マスク付き)
st1w { z2.s }, p0, [x2]         ; 結果をストア

RISC-V ベクトル(RVV)

可変長でシンプル設計。

vl1re32.v v0, (x0)              ; ベクトルレジスタ v0 にロード
vl1re32.v v1, (x1)              ; ベクトルレジスタ v1 にロード
vadd.vv v2, v0, v1              ; ベクトル加算
vs1r.v v2, (x2)                 ; ベクトルレジスタ v2 をストア

割り込みと例外

CPU は自分の命令だけを見ていればよいわけではありません。外からの出来事に反応する必要があります。

割り込みの例:

直感的には、作業中に急ぎの連絡が飛び込んでくる感じです。

割り込み vs 例外

  • 割り込み(Interrupt):外部からの非同期イベント(タイマ、I/O)
  • 例外(Exception):実行中の命令から生じる同期イベント(ページフォルト、ゼロ除算)

割り込み処理のフロー

  1. 割り込み信号を受信
    • 割り込みコントローラ(APIC など)がベクタ番号を決定
  2. 現在の文脈を退避
    • PC(プログラムカウンタ)、フラグ、スタックポインタ等をスタックに保存
  3. 割り込みハンドラへジャンプ
    • ベクタ番号に応じた割り込みハンドラのアドレスへ
  4. 割り込みハンドラ実行
    • 割り込み原因に対応する処理
  5. 復帰
    • iret(return from interrupt)で元の実行位置に戻る
    • 退避した文脈を復元
プログラム実行中
  ↓
[割り込み信号]
  ↓
PC, レジスタをスタックに保存
PC = ハンドラアドレス
  ↓
割り込みハンドラ実行
  ↓
iret で復帰
PC, レジスタをスタックから復元
  ↓
プログラム再開

優先度と割り込みの許可/禁止

優先度

割り込みソース(タイマ、キーボード等)に優先度を付与。高優先度の割り込みは低優先度の処理中でも割り込み可能。

割り込み許可フラグ

  • x86-64:RFLAGS レジスタの IF(Interrupt Flag)ビット
  • ARM64:PSTATE の I、F ビット
  • RISC-V:mstatus の MIE(Machine Interrupt Enable)ビット
IF = 1(許可):割り込み受け取り可能
IF = 0(禁止):割り込み無視

クリティカルセクション(ロック保護下など)では割り込みを禁止。


セキュリティ拡張

x86-64 セキュリティ機能

SGX(Software Guard Extensions)

CPU 内の信頼できる実行領域(Enclave)を提供。

オペレーティングシステム(信頼できない)
  ↓
Enclave(CPU が検証した信頼できるコード)
  ↓
システムメモリ(暗号化)
  • データを暗号化してメモリに格納
  • CPU 内でのみ復号化
  • 特権ソフトウェア(OS、ハイパーバイザ)からも保護

用途:DRM、機密計算、認証キー保護

セキュリティ上の問題:Spectre/Meltdown の時間攻撃で Enclave の秘密を漏らす可能性が報告

TDX(Trust Domain Extensions)

仮想マシン用のセキュリティ拡張。

  • VM を CPU が検証
  • ハイパーバイザからの攻撃から保護

AMD セキュリティ機能

SEV(Secure Encrypted Virtualization)

VM 用暗号化。

  • VM のメモリを暗号化
  • ハイパーバイザからの攻撃から保護

SEV-SNP(Secure Nested Paging)

ページテーブル整合性の保証を追加。

ARM64 セキュリティ機能

TrustZone

2 つの実行環境を分離:

  1. Rich Execution Environment(REE):通常の OS(スマートフォン OS など)
  2. Trusted Execution Environment(TEE):信頼できるコード(暗号化、支払い認証など)
  • REE からは TEE の命令/データへアクセス不可
  • TEE からは REE へアクセス可(一方向)

ARM CCA(Confidential Computing Architecture)

TrustZone の次世代。VM 向けセキュリティ。

Spectre / Meltdown

これらは投機的実行を悪用する攻撃。

Meltdown

CPU が投機的に未許可メモリを読む → メモリアクセス権限チェック前にキャッシュに乗る → 例外で巻き戻される

タイミング攻撃で、キャッシュに何が乗っているかを推測 → メモリ内容を漏らす

対策

  • KPTI(Kernel Page Table Isolation):カーネルメモリを完全に隔離
  • CPU マイクロコード更新

Spectre

分岐予測を誤らせる → 不正な推測実行 → キャッシュを汚染 → タイミング攻撃で漏らす

亜種

  • Spectre v1:ローカルメモリ読み取り(フェンス命令で軽減)
  • Spectre v2:間接分岐のブルート フォース(ibpb, ibrs マイクロコード)
  • SpectreRSB:リターンスタックバッファの誤予測(retpoline)

対策

  • Retpoline:間接ジャンプ/リターンの保護
  • IBRS/IBPB:分岐予測バッファ隔離
  • STIBP:シングルスレッド間接分岐予測
  • バリア命令(lfence など)

仮想化拡張

物理 CPU 上で複数の VM を安全かつ効率的に動かすための機能。

VT-x(Intel Virtualization Technology)

ハイパーバイザ用の特殊モード:

  • VMX root mode:ハイパーバイザが動く
  • VMX non-root mode:ゲスト OS が動く
VM Entry:root → non-root へ移行
ゲスト実行中...
VM Exit:non-root → root へ戻る(ゲストが特権操作を試みた等)

特殊なレジスタ

  • VMCS(Virtual Machine Control Structure):VM の状態と動作パラメータ
  • EPT(Extended Page Tables):ゲスト物理 → ホスト物理アドレス変換(2 段階ページテーブル)

AMD-V(AMD Virtualization)

VT-x と同様。

  • SVM(Secure Virtual Machine):特殊なモード
  • nPT(nested Page Tables):2 段階ページテーブル

ARM64 仮想化

EL(Exception Level)

ARM64 は 4 段階の特権レベル:

EL0:アプリケーション
EL1OS(ゲスト OS)
EL2:ハイパーバイザ
EL3:セキュアモニタ

2 段階アドレス変換

  • ゲスト仮想 → ゲスト物理(ゲスト OS のページテーブル)
  • ゲスト物理 → ホスト物理(ハイパーバイザの Stage 2 テーブル)

RISC-V 仮想化

Hypervisor Extension(H 拡張)。

VM が実行可能な特殊モード:VU(Virtual User)、VS(Virtual Supervisor)


電力管理

現代 CPU は、消費電力制御が重要なテーマ。

DVFS(Dynamic Voltage and Frequency Scaling)

クロック周波数と電圧を動的に調整。

高負荷時:高周波(3+ GHz)、高電圧 → 高性能、高消費電力
低負荷時:低周波(1 GHz以下)、低電圧 → 低性能、低消費電力

消費電力 ∝ f × V^2(周波数と電圧の 2 乗に比例)

P-state(パフォーマンスレベル)

OS が各スレッドのパフォーマンス要求を指定。CPU がそれに応じて周波数/電圧を調整。

Intel HWP(Hardware P-States)

ハードウェアが自動的に周波数を調整。OS が目標性能指標を指定するだけ。

C-state(クロック制御)

コアがアイドル時に、より低い消費電力状態へ遷移。

C0:通常実行
C1:クロック停止、コアウェイク可能(数マイクロ秒で復帰)
C2:より低い電圧
C3:最も低い電圧(復帰に数百マイクロ秒)

Power Gating

未使用のコアを完全に電源 OFF。

  • ARM big.LITTLE:低電力コアのみ使用
  • Intel P-core/E-core:E-core のみ使用可能

実機実例

Intel Core i9-14900K

仕様

  • ISA:x86-64(+ AVX-512 なし)
  • P-core:8 個(Performance)
  • E-core:8 個(Efficiency)
  • L1d/L1i:各 16 個コア × 48 KB
  • L2:各コア × 1.25 MB
  • L3:36 MB(P-core 用 20 MB + E-core 用 16 MB)
  • 動作周波数:P-core 3.2-6.2 GHz、E-core 2.4-5.6 GHz
  • TDP:125W(P-core)+ 8W(E-core)× 8 = 189W

パイプライン

  • P-core:14 段(Raptor Lake)
  • スーパースカラ:4-6 μ-ops/サイクル

分岐予測

  • TAGE-like 予測器
  • 64 KB 分岐履歴

AMD Ryzen 9 7950X(3D V-Cache 版)

仕様

  • ISA:x86-64(+ AVX-512 なし)
  • コア:16 個(全て Performance レベル)
  • L1d/L1i:各 16 個コア × 32 KB
  • L2:各コア × 512 KB
  • L3:96 MB(64 MB + 32 MB 3D V-Cache)
  • 動作周波数:4.5-5.7 GHz
  • TDP:170W

3D V-Cache

  • 従来の L3 の上に垂直積層
  • 3D TSV(Through-Silicon Via)で接続
  • 有効容量が 2 倍(キャッシュミス率大幅低下)

Apple M3 Max

仕様

  • ISA:ARM64(+ SVE なし)
  • 性能コア(Performance):8 個
  • 効率コア(Efficiency):2 個
  • GPU:16 コア(M3 Max)
  • Neural Engine:16 コア
  • L1d:各コア × 64 KB
  • L1i:各コア × 128 KB
  • L2:各クラスタ × 12 MB
  • L3:メイン DRAM(Unified Memory)
  • 動作周波数:3.0-4.3 GHz
  • 電源:~30W(ノートPC)

特殊な特徴

  • Unified Memory:CPU と GPU が同じメモリ階層を使用(コピーなし)
  • AMX:Advanced Matrix eXtensions(機械学習用)
  • Neural Engine:AI アクセラレータ(独立コア)

AWS Graviton3

仕様

  • ISA:ARM64
  • コア:64 個
  • L1d/L1i:各コア × 64 KB
  • L2:各コア × 1 MB
  • L3:32 MB(クラスタ共有)
  • 動作周波数:3.5 GHz
  • SVE サポート(128 ビット)

用途

  • AWS EC2 インスタンス向け
  • 電力効率重視

IBM POWER10

仕様

  • ISA:POWER(64 ビット)
  • コア:最大 12 個
  • L1d/L1i:各コア × 32 KB
  • L2:各コア × 512 KB
  • L3:各コア × 8 MB
  • L4:SRAM ベース(最大 840 MB)
  • 動作周波数:4.1-4.4 GHz

特徴

  • 浮動小数点ユニットが強い
  • ベクトル拡張(VSX)
  • 企業向けサーバー向け

2025-2026 動向

チップレット化の加速

  • AMD Ryzen:複数の CXU(Core Complex Unit)を 1 つのダイに統合
  • Intel Meteor Lake:複数のタイル(P-tile, E-tile, GPU tile)を 1 パッケージに統合

利点

  • 製造不良率低下(各タイルの面積が小さい)
  • 異なるプロセスで最適化(GPU は低周波、低電圧が可能)
  • コスト低下

課題

  • タイル間の通信遅延
  • キャッシュコヒーレンシ複雑化

3D メモリ積層の拡大

  • 3D V-Cache:AMD が先行
  • Intel も新 Xeon で検討
  • HBM(High Bandwidth Memory)との統合

AI アクセラレータ統合

  • Apple Neural Engine
  • Google Tensor Processing Unit(TPU)
  • Amazon Trainium/Inferentia

専門的なハードウェアで機械学習を高速化。

量子 CPU の進展

  • IBM Quantum
  • Google Willow(ただし NISQ 段階)
  • IonQ(イオントラップ方式)

エラー訂正が課題(100 論理キュービットまで遠い)。

ニューロモーフィックチップ

  • Intel Loihi 2
  • IBM TrueNorth

生物的な脳構造を模倣した計算。応用例は限定的だが研究進行中。

ARM vs x86-64 の進化競争

ARM

  • 省電力で高性能を両立
  • スマートフォン、タブレット、サーバーへ進出
  • 次世代ゲーム機候補

x86-64

  • 後方互換性を維持
  • 既存ソフトウェア資産が膨大
  • 仮想化サポートが強い

両者は徐々に特徴が収束。

RISC-V の産業化進行

  • 中国政府の後押し(海外製 ISA への依存排除)
  • Western Digital が RISC-V を採用(SSD コントローラ)
  • IonQ(量子)が古典部分に RISC-V

ただし 2026 時点では、実装数は ARM/x86-64 に遠く及ばない。


性能の見方

CPU の性能はクロック周波数だけでは決まりません。

  • 命令数
  • 分岐予測の当たり外れ
  • パイプライン効率
  • レジスタ利用
  • SIMD 活用
  • メモリ待ち

などが絡みます。

IPC と CPI の発想

CPU 性能の見方では、

  • IPC: 1 サイクルあたり何命令進むか
  • CPI: 1 命令あたり何サイクルかかるか

という考え方がよく出ます。クロックが高くても IPC が低ければ伸びませんし、逆にクロックが少し低くても効率のよい実行で勝つことがあります。

性能 = IPC × クロック周波数
     = (命令数 / CPI) × クロック周波数

CPU バウンドとメモリバウンド

  • CPU バウンド: 計算そのものが支配的
  • メモリバウンド: メモリ待ちが支配的

この違いを見ないまま最適化すると、頑張る場所を間違えます。CPU バウンドなら演算や分岐を見直し、メモリバウンドならデータ配置や局所性を見るほうが効きます。

性能観察の入口

flowchart TD A[遅い] --> B[CPU 使用率を見る] B --> C[計算が重い] B --> D[待ちが多い] C --> E[CPU バウンドを疑う] D --> F[メモリ / I/O 待ちを疑う]

レイテンシとスループット

  • レイテンシ: 1 回の処理にかかる時間
  • スループット: 単位時間あたりの処理量

パイプライン化は、レイテンシよりスループット改善に効くことが多いです。

SPEC ベンチマーク

業界標準の性能評価。

SPEC CPU2017

  • 整数計算(CINT)と浮動小数点計算(CFP)
  • スタンドアロン PC/ワークステーション向け

SPEC Cloud Infrastructure

  • クラウド環境での性能評価

Geekbench

消費者向けのマルチプラットフォームベンチマーク。

  • iOS、macOS、Linux、Windows 対応
  • シングルコア/マルチコア性能を測定

MLPerf

機械学習ワークロード中心。

  • 推論性能
  • 学習性能
  • メモリ効率

実務ケーススタディ

ケース 1:Python のループが遅い

同じ処理でも、Python で要素ごとに回すのと、NumPy のベクトル化演算へ渡すのでは大きな差が出ることがあります。

# 遅い(Python ループ)
import time
a = [i for i in range(10_000_000)]
b = [i for i in range(10_000_000)]
start = time.time()
c = [a[i] + b[i] for i in range(len(a))]
end = time.time()
print(f"Python: {end - start:.2f}s")  # 約 1-2 秒

# 速い(NumPy ベクトル化)
import numpy as np
a = np.arange(10_000_000)
b = np.arange(10_000_000)
start = time.time()
c = a + b
end = time.time()
print(f"NumPy: {end - start:.3f}s")  # 約 0.01 秒(100 倍高速)

ここでは、

  • インタプリタのオーバーヘッド(Python コードを逐一デコード)
  • C 実装への移譲(NumPy は C で実装)
  • SIMD の活用(NumPy + SIMD で一括処理)

が重なっています。

ケース 2:分岐だらけのフィルタ処理

データに応じて分岐が頻繁に変わる処理は、分岐予測が外れやすく、思ったより伸びません。

// 遅い(分岐多い)
for (int i = 0; i < N; i++) {
    if (data[i] > threshold) {
        result[i] = data[i] * 2;
    } else if (data[i] < -threshold) {
        result[i] = data[i] / 2;
    } else {
        result[i] = 0;
    }
}

// 速い(分岐削減)
for (int i = 0; i < N; i++) {
    int sign = (data[i] > threshold) - (data[i] < -threshold);
    result[i] = sign * (sign > 0 ? data[i] * 2 : data[i] / 2);
}

アルゴリズムだけでなく、条件の並べ方やデータの並びも効いてきます。

ケース 3:クロックが高いのに体感が速くない

メモリ待ち、キャッシュミス、分岐予測ミス、I/O 待ちが支配的なら、クロックだけでは勝てません。

Intel Core i9-14900K(6.2 GHz)
vs
AMD Ryzen 9 7950X(5.7 GHz、3D V-Cache)

ゲーム:AMD が速い(L3 キャッシュヒット率高)
スティープスロープ回転(計算集約):Intel が速い(高周波)

ワークロード特性にマイクロアーキテクチャが合致することが重要。

ケース 4:CPU 使用率は高いのにスケールしない

マルチコア環境で CPU 使用率が高く見えても、性能が素直に伸びないことがあります。

ありがちな理由は次です。

  • 共有ロックが強く、スレッドどうしが待っている
  • False Sharing でキャッシュラインが行き来している
  • NUMA をまたぐメモリアクセスが多い
  • 実際にはメモリ帯域が先に飽和している
flowchart TD A["CPU 使用率が高い"] --> B["本当に計算しているか"] B --> C["Yes: CPU バウンドの可能性"] B --> D["No: ロック / メモリ / NUMA を疑う"] D --> E["False Sharing?"] D --> F["リモートメモリアクセス?"] D --> G["帯域飽和?"]

ここで CPU の知識が効くのは、「コアが忙しい」のと「仕事が前に進んでいる」のは別だと分かることです。


発展ルート

  • CPU マイクロアーキテクチャ
  • 分岐予測器
  • Out-of-Order 実行
  • GPU / アクセラレータとの比較

それぞれ何を深めるか

  • CPU マイクロアーキテクチャ: 実行ユニット、リネーミング、リオーダバッファなど内部構造を見る
  • 分岐予測器: 予測の当たり方が性能へどう効くかを学ぶ
  • Out-of-Order 実行: プログラム順と実行順のズレをどう整えるかを見る
  • GPU / アクセラレータとの比較: CPU が得意な仕事と、別ハードウェアが得意な仕事を見分ける

ミニ比較表

概念 何を良くするか よくある誤解
パイプライン スループット 1 命令のレイテンシを必ず下げると思う
分岐予測 停止を減らす 分岐そのものを消していると思う
SIMD 同種データの一括処理 スレッド並列と同じだと思う
ISA 外から見える命令の約束 内部実装そのものだと思う
Out-of-Order 実行 依存のない命令を前倒し プログラム順を壊してよいと思う
3D V-Cache キャッシュ容量 遅延が短くなると思う
SMT 未使用リソース活用 すべてのワークロードで高速化すると思う

練習問題

  1. 同じ ISA でも CPU ごとに性能差が大きく出る理由を説明してください。
  2. 分岐予測ミスが増えると、なぜ体感性能が落ちるのか説明してください。
  3. SIMD が効きやすい処理と効きにくい処理を 1 つずつ挙げてください。
  4. Out-of-Order 実行がなぜ複雑な仕組みなのに採用されるのか説明してください。
  5. マルチコア CPU でのメモリ一貫性の問題と、その解決方法を説明してください。

FAQ

GHz が高ければ速いのか

一要素ではありますが、それだけでは決まりません。IPC、キャッシュ、分岐予測、メモリ階層、コア数、ワークロードの性質が大きく効きます。

コア数が多ければすべて速くなるのか

なりません。仕事が分けられない部分、ロック競合、メモリ帯域、I/O 待ちがあると伸びは頭打ちになります。

SIMD は自動で効くのか

コンパイラやライブラリが自動化してくれることはありますが、データの並び方や分岐の少なさが重要で、いつでも勝手に最大限効くわけではありません。

ARM は x86-64 より遅いのか

ISA が異なるだけで、マイクロアーキテクチャと用途しだい。Apple M3 は x86-64 競合製品と遜色ない。

投機的実行は完全に無効化すべきか

Spectre/Meltdown の脅威は実在しますが、完全無効化では性能が 5-30% 低下。リスク許容度とセキュリティのバランスが重要。

コラム

CPU は「とにかく暇を減らしたい」

先読み、予測、前倒し、並行処理。現代 CPU の工夫をひとことで言うと、待ち時間を減らすための総力戦です。

メモリアクセスが遠い(数百サイクル)のに対し、計算は速い(数サイクル)。この非対称性を埋めるため、CPU は必死に「何か仕事を見つけて流す」ようにできています。

ISA は「インターフェース契約」

ISA は CPU ベンダと人間(コンパイラライタ、アセンブリプログラマ)の契約。

「この命令を実行したら、この結果が出ます」という約束。実装はベンダの自由。

同じ x86-64 でも、Intel と AMD、さらには世代間で内部実装は全く異なります。


学習ロードマップ

CPU を学ぶ順番

  1. 命令実行の基本(Fetch-Decode-Execute)
  2. レジスタと ISA(x86-64, ARM64, RISC-V の違い)
  3. パイプライン(5 段~深いパイプライン)
  4. 分岐予測と Out-of-Order 実行
  5. SIMD(SSE, AVX, NEON, SVE)
  6. 割り込み、特権モード、システムコール
  7. マルチコア、キャッシュコヒーレンシ
  8. セキュリティ拡張、仮想化

最初から最適化の細部へ行くより、「何がどこで詰まるか」という観点で積むと理解しやすいです。

どこでメモリの話と合流するか

CPU 単体の工夫だけでは性能が決まりません。実務ではかなり早い段階で、

  • キャッシュヒット率
  • メモリ帯域
  • データ局所性

といった話へ合流します。

実務での見方

プロファイル結果を読むとき

CPU の知識があると、

  • 分岐が多いのか
  • ループが重いのか
  • システムコール待ちなのか
  • メモリ待ちなのか

を考えやすくなります。

低レベル最適化を疑う前に

まずはアルゴリズム、I/O、データ構造の方が効くことも多いです。CPU の知識は万能の最適化テクニックではなく、「どこが本当のボトルネックか」を見誤らないために効きます。

実務チェックリスト

  • 遅さは CPU バウンドかメモリ / I/O バウンドか
  • 分岐が多すぎないか
  • 同じ演算を大量データへ適用していないか
  • システムコール待ちやロック待ちではないか
  • 低レベル最適化の前にアルゴリズムを見直したか
  • マルチコア利用で False Sharing は起きていないか
  • キャッシュラインアライメントは適切か

模範解答例

例:ISA とマイクロアーキテクチャの違い

ISA は CPU が外部へ見せる命令の約束で、マイクロアーキテクチャはその命令を内部でどう実装するかです。同じ ISA でも、パイプライン、キャッシュ、分岐予測器、実行ユニットの構成が違えば性能は変わります。

例)x86-64 ISA は Intel Core i9、AMD Ryzen 9、古い Pentium でも実装されていますが、IPC や クロック周波数の効率が大きく異なります。

例:分岐予測ミスが遅さにつながる理由

CPU は予測に基づいて先回りして命令を流します。予測が外れると、先に進めていた仕事を捨ててやり直す必要があるため、パイプライン効率が落ちます。

パイプラインが長い(14~18 段)ほど、投機実行の距離が長いため、ミス時の損失が大きくなります。

例:Out-of-Order 実行がなぜ採用されるのか

プログラムで書かれた順序でしか実行しない場合、依存関係がある命令の完了を待つ間、他のリソース(ALU など)が遊んでしまいます。Out-of-Order 実行なら、遊んでいるリソースで別の無関係な命令を先に進める(前倒し)ことで、全体のスループットを向上させます。

ただしハードウェア複雑性が大幅に増えるため、低電力が必須な環境(モバイル)では in-order 実行(ARM Cortex-A53 等)が選ばれることもあります。

章末まとめ

  • CPU は命令を実行するだけでなく、待ちを隠すために先読みと調停を行う
  • ISA は約束、マイクロアーキテクチャは実装
  • パイプライン、分岐予測、Out-of-Order 実行はスループット改善の中心
  • SIMD は同種データへの一括演算に強い
  • CPU の性能はメモリ階層、キャッシュコヒーレンシ、マルチコアロジックとも切り離せない
  • セキュリティと性能はトレードオフ(投機的実行 vs Spectre/Meltdown)
  • 仮想化拡張により、効率的なマルチテナント実行が実現
  • 電力管理が現代 CPU では重要なテーマ
  • ISA の多様化(ARM, RISC-V, x86-64)で競争が活発化

次に読むなら

  • メモリとキャッシュ: メモリ
  • OS の詳しい話: OS

補足

第5章 コンピュータアーキテクチャ

初心者向けメモ
プログラムが動く「物理的な舞台」を見る章です。CPU・メモリ・ストレージの速度差、キャッシュ、ISA、パイプラインといった概念を押さえ、「なぜ同じコードでもハードウェアによって速度が変わるのか」がわかるようになります。
要点
コンピュータは一枚岩ではなく、速度も役割も違う部品の組み合わせです。本章では CPU・キャッシュ・メモリの関係から性能の直感を作ります。

この章が実務で役立つ場面

  • 速いはずのコードが想像より遅い理由を考える
  • キャッシュヒット率やメモリアクセスの影響を理解する
  • SIMD、分岐予測、局所性が効く場面を見抜く

5.1 CPU・メモリ・ストレージ

プログラムは CPU が実行し、命令やデータはメモリに置かれ、長期保存はストレージに行います。

【図10】ストレージ・メモリ・CPU のデータの流れ:

flowchart LR A["ストレージ"] --> B["メモリ"] B --> C["CPU"] C --> B

5.2 命令実行の基本

CPU は大ざっぱに言うと、

  1. 命令を読む
  2. 解釈する
  3. 実行する

を繰り返します。これを fetch-decode-execute サイクルと呼びます。

5.2.1 割り込みはなぜ必要か

CPU がずっと自分の命令だけを淡々と実行していると、外からの出来事にうまく反応できません。そこで使われるのが 割り込み です。

割り込みは、

  • キーボード入力が来た
  • ディスク I/O が終わった
  • タイマが発火した

といった出来事を CPU に知らせる仕組みです。

直感的には、「いまの作業を少し中断して、急ぎの連絡に対応する」感じです。OS のスケジューリングや I/O 完了通知は、この仕組みの上に成り立っています。

5.3 キャッシュ

CPU とメモリには速度差があるため、途中にキャッシュが入ります。

  • L1: 非常に速いが小さい
  • L2 / L3: 少し遅いが大きい

データが近くにまとまっていると速くなりやすい、というのが重要な直感です。

【図30】メモリ階層と速度(2025年の典型値):

flowchart LR A["レジスタ / ~0.3 ns / 数十 B"] --> B["L1 キャッシュ / ~1 ns / 32 KB"] B --> C["L2 キャッシュ / ~4 ns / 256 KB"] C --> D["L3 キャッシュ / ~10 ns / 数 MB"] D --> E["DRAM / ~80 ns / 数 GB〜TB"] E --> F["SSD (NVMe) / ~25 μs / TB"] F --> G["HDD / ~10 ms / TB"]

5.3.1 局所性(Locality)の原理

キャッシュが効く理由は2種類の局所性にある:

  • 時間局所性:最近アクセスしたデータは近く再びアクセスされる(ループ変数)
  • 空間局所性:あるアドレスにアクセスしたら近くのアドレスにもアクセスされる(配列走査)

配列の行優先(row-major, C/C++/Python)列優先(column-major, Fortran/MATLAB) かで性能が 10 倍変わることがある。

// 遅い(C は行優先なので、列を外側ループにすると空間局所性が崩れる)
for (int j = 0; j < N; j++)
  for (int i = 0; i < N; i++)
    sum += a[i][j];

// 速い(行優先に合致)
for (int i = 0; i < N; i++)
  for (int j = 0; j < N; j++)
    sum += a[i][j];

5.3.2 パイプラインと分岐予測

現代 CPU は 1 命令を複数段階(fetch, decode, execute, memory, writeback)に分解し、異なる命令を並行実行する パイプライン を使う。

  • 分岐予測(Branch Prediction):条件分岐の結果を予測してパイプラインを埋める。ミスすると 10〜20 サイクルの無駄
  • 投機的実行(Speculative Execution):予測に基づいて先行実行。Meltdown/Spectre 脆弱性はここに起因
  • アウトオブオーダー実行:依存関係のない命令を順序を変えて実行

5.4 SIMD と並列化

SIMD(Single Instruction Multiple Data) は 1 命令で複数データを処理する機構:

  • x86: MMX、SSE、AVX、AVX-512
  • ARM: NEON、SVE
  • RISC-V: V 拡張

画像処理、機械学習、暗号化、圧縮で劇的な高速化。numpy の内部や LLM の行列演算はすべて SIMD を駆使している。

5.5 レジスタ

レジスタは CPU 内にある極めて高速で小さな記憶領域です。計算中の値やアドレス、制御情報を保持します。

5.6 命令セットアーキテクチャ

CPU ごとに理解できる命令の集合があり、これを ISA と呼びます。

  • x86-64
  • ARM64
  • RISC-V

同じ C プログラムでも、最終的には CPU ごとに違う機械語へ変換されます。

5.7 パフォーマンスの見方

遅さの原因は「CPU が遅い」だけではありません。

  • メモリアクセスが多い
  • キャッシュミスが多い
  • 分岐予測が外れる
  • I/O 待ちが長い

といった要因が絡みます。

5.7.1 レイテンシとスループット

アーキテクチャでも重要なのが、

  • レイテンシ: 1 回の処理が終わるまでの時間
  • スループット: 単位時間あたりにどれだけ処理できるか

の区別です。

たとえばパイプライン化は、個々の命令のレイテンシを劇的に減らすとは限りませんが、全体のスループットを大きく改善できます。

この区別は後で、ネットワークやデータベースでも何度も出てきます。

5.8 よくある誤解

よくある誤解
高速化は CPU だけの問題ではありません。実際にはメモリ階層や I/O の影響が非常に大きいです。

5.9 例題

例題1: キャッシュが効くと速くなりやすいのはなぜか。

解説: CPU に近い高速な記憶領域からデータを読めるからです。

例題2: 同じプログラムでも x86-64 と ARM64 で最終バイナリが違う理由を述べよ。

解説: CPU が理解する命令セットが異なるからです。

5.10 練習問題

  1. CPU 内で最も高速な記憶領域は何か。
  2. 長期保存に使うのはメモリとストレージのどちらか。
  3. fetch-decode-execute の3段階を挙げよ。

5.11 練習問題の答え

  1. レジスタ
  2. ストレージ
  3. 命令を読む、解釈する、実行する

まとめ

CPU は、命令をどう読み、どう並べ、どう速くするかを理解するための中心的な実行基盤です。ISA、パイプライン、分岐予測、SIMD、メモリ階層をつなげて見ると、性能の見方が具体的になります。