CPU
目次
- 概要
- CPUの歴史
- CPUの役割
- 命令実行の基本
- レジスタとISA
- ISA(Instruction Set Architecture)
- ISA各種の詳細
- パイプラインと分岐予測
- 高度な実行技法
- キャッシュとメモリ階層
- SIMDと並列化
- マルチコアと並列性
- 浮動小数点とベクトル拡張
- 割り込みと例外
- セキュリティ拡張
- 仮想化拡張
- 電力管理
- 実機実例
- 現在の動向
- 性能の見方
- 実務ケーススタディ
- 発展ルート
- ミニ比較表
- FAQ
- コラム
- 学習ロードマップ
- 実務での見方
- 実務チェックリスト
- 模範解答例
- 次に読むなら
- 補足
- CPU仕様と性能資料の補足
- まとめ
- 参考文献
概要
命令実行・パイプライン・性能の見方
CPUは、プログラムを実際に動かす中心です。CPUがどう命令を読み、どう並列化し、なぜ速い遅いが起きるのかを順に見ていきます。
命令列そのものを具体的に読みながら進みたいときは、アセンブラ を横に置くと、命令、レジスタ、スタック の像がかなり固まります。
この章で重視すること
- CPUをブラックボックスではなく、段階的な実行装置として見る
- 性能をクロック周波数だけで語らない
- 分岐予測やSIMDのような現代CPUの工夫を直感でつかむ
- CPUの歴史と実装を通じて、設計の進化を知る
- ISAと マイクロアーキテクチャの違いを深く理解する
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)
パイプラインの登場(1980-1990年代)
Intel Pentium(1993)
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です。
CPUの中核的な役割
-
命令フェッチ(Instruction Fetch)
- メモリから次の命令を読み出す
- プログラムカウンタ(PC)を進める
-
命令デコード(Instruction Decode)
- バイナリ形式の命令を解釈
- どの演算を、どのデータで行うか決定
-
命令実行(Execute)
- ALU(Arithmetic Logic Unit)で計算
- メモリアクセスの開始
- 分岐先の計算
-
メモリアクセス(Memory Access)
-
結果ライトバック(Write Back)
- 計算結果をレジスタに書き込む
- 次の命令へ値を引き渡す
1命令が流れるときに何が起きるか
入門では fetch -> decode -> execute と3語で済ませがちですが、実際にはCPUは1命令に対してかなり多くの判断をしています。
たとえば add rax, rbx のような単純な命令でも、CPUは大まかに次を行います。
- 次に読む命令アドレスを決める
- 命令バイト列を命令キャッシュから取る
- 命令をデコードして「どの演算器を使うか」を決める
- 必要なオペランドがレジスタやキャッシュにあるか確認する
- 実行ユニットへ投入する
- 結果を一時的に保持し、最終的にレジスタへ反映する
【図2-1】1命令の見え方とCPU内部の流れ:
ここで大事なのは、プログラマから見える順番と、CPU内部での一時的な処理順が必ずしも同じではないことです。後で出てくる Out-of-Order実行 や 投機的実行 は、この「見える順番」と「内部で前倒しする順番」を分ける工夫だと思うと理解しやすくなります。
CPUが見ている「状態」
CPUが仕事を進めるときに見ている状態は、大きく2層あります。
この区別はとても重要です。プログラマは通常、前者だけを意識すれば十分ですが、性能やセキュリティを考えると後者の存在が効いてきます。
| 観点 | 例 | 何に効くか |
|---|---|---|
| アーキテクチャ状態 | RAX, PC, メモリ内容 |
正しさ、互換性 |
| マイクロアーキテクチャ状態 | 分岐予測履歴、ROB、TLB | 性能、投機実行、副作用 |
CPUの3つの側面
CPUは、単純に「計算する部品」では理解できません。3つの側面を同時に持っています。
1. 計算エンジン
2. 待ち隠蔽の工夫
- キャッシュの活用
- プリフェッチ
- パイプライン化
- 分岐予測
- アウト・オブ・オーダー実行
3. メモリマネージャ
- ページテーブルウォーク
- TLB(Translation Lookaside Buffer)
- キャッシュコヒーレンシ
- メモリ順序付け
命令実行の基本
fetch-decode-executeサイクル
典型的には、
- 命令を読む
- 解釈する
- 実行する
を繰り返します。これが fetch-decode-execute サイクルです。
ただし現代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)
RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSPR8-R15- 全16個(各64ビット)
X0-X30(31個の汎用レジスタ)X31はスタックポインタまたはゼロレジスタ- 各64ビット
X0-X31(32個)X0はハードウェアゼロX1はリターンアドレス- 各64ビット(RV64I)
特殊レジスタ
RIP:命令ポインタ(次に実行する命令のアドレス)RFLAGS:フラグレジスタ(キャリー、ゼロ、オーバーフロー、符号等)CR0-CR4:制御レジスタ(ページング有効化、保護モード等)MSR(Model-Specific Register):CPU固有の設定
PC:プログラムカウンタSP:スタックポインタLR:リンクレジスタ(関数呼び出しの戻り先)NZCV:コンディションフラグ- :例外からの戻り先
PC:プログラムカウンタSP(X2):スタックポインタRA(X1):戻りアドレスMSTATUS:マシン状態レジスタ
SIMDレジスタ
V0-V31:可変長(RVV)VLEN:ベクトルレジスタの長さ
ISA(Instruction Set Architecture)
ISA(Instruction Set Architecture)は、CPUが理解する命令の集合です。
同じ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
起源
特徴
- 複雑な命令セット(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シリーズのみ)
実装例
ARM64(ARMv8-A)
起源
特徴
- シンプル設計(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実装を設計・製造可能
標準拡張
実装例
- 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命令を速く終わらせる」より、「たくさんの命令を流し続ける」ための工夫です。洗濯、乾燥、たたみを別の工程で並行に進める流れ作業を思うと近いです。
このとき流れている具体的な命令の姿は、アセンブラ の 具体的なコード例 を見るとイメージしやすくなります。
各段階をF-D-E-M-W(Fetch-Decode-Execute-Memory-WriteBack)と呼びます。
従来の5段パイプライン(Intel Pentium等)
- Fetch:命令メモリから命令を読み込み
- Decode:命令を解釈し、制御信号を生成
- Execute:ALUで計算、アドレス計算
- Memory:キャッシュ/メモリアクセス
- WriteBack:結果をレジスタに書き込み
より深いパイプライン(Intel Core等)
クロック周波数を上げるために、パイプラインをさらに細分化します。
パイプラインが深いほど、各段階は簡単になりますが、依存関係の解決やプリフェッチに複雑さが増します。
パイプラインハザード(段階間の依存性問題)
パイプラインが効率よく動かない場面があります。
構造的ハザード(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】理想的なパイプラインとストール:
ストールが痛いのは、遅れた1命令だけでなく、その後ろの命令列まで巻き添えになりやすいからです。分岐予測ミスやL1ミスが体感性能に効くのは、この「後続の流れも止める」性質があるからです。
フォワーディングがないと何が起きるか
データハザードは、教科書で読むだけだと抽象的です。次の2命令を考えます。
add r1, r2, r3
mul r4, r1, r5
2行目は1行目の r1 をすぐ使いたいのですが、1行目の結果がまだレジスタへ正式に書き戻されていないかもしれません。ここでフォワーディングがないと、2行目は待つしかありません。
この「書き戻し完了を待たず、途中結果を横流しする」工夫が、現代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)
実行順序をプログラム順に戻すための重要な構造。
- 命令がフェッチされると、ROBに予約(enqueue)
- オペランドが利用可能になると、実行ユニットで実行
- 実行は順序を無視(OoO)
- 完了したら、ROBに結果を記録
- ROBから命令ポインタ順に「コミット」(プログラム順に見える状態にする)
- コミット時に、例外チェック、メモリ順序付けを行う
レジスタリネーミング
物理レジスタの数が論理レジスタより多い場合、レジスタ依存を減らせます。
例: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実行のコスト
スーパースカラ実行(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の話は自然にキャッシュやメモリ階層へつながります。
キャッシュの階層構造
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つの状態を持つ:
- Modified(修正済み):このコアだけが有効なコピーを持ち、メモリと異なる
- Exclusive(独占的):このコアだけが有効なコピーを持ち、メモリと同じ
- Shared(共有):複数のコアが同じ値のコピーを持つ
- Invalid(無効):このコアのコピーは無効(他のコアが修正した)
MOESIプロトコル
MESIにOwned状態を追加。AMD EPYCなど一部で採用。
Directoryベースコヒーレンシ
大規模なマルチコアシステムでは、全コアのコピーを追跡するメモリディレクトリを持つ。
キャッシュミスの種類
- コンパルサリミス(Compulsory Miss):初めてアクセスするデータ(避けられない)
- 容量ミス(Capacity Miss):キャッシュが満杯で、古いデータが廃棄された
- 競合ミス(Conflict Miss):複数のアドレスが同じキャッシュラインにマップされる
遅延の桁感を持つ
CPUの話でつまずきやすいのは、どれがどれくらい遅いのか の感覚がないことです。正確なサイクル数は世代で変わりますが、大まかにした桁感を持つだけでもかなり理解しやすくなります。
| 層 | 典型的な遅延感覚 | 何が起きるか |
|---|---|---|
| レジスタ | ほぼ即時 | すぐ演算へ渡せる |
| L1 | 数サイクル | 通常はここで済ませたい |
| L2 | 十数サイクル | 少し待つがまだ許容しやすい |
| L3 | 数十サイクル | 目に見えて重くなる |
| DRAM | 数百サイクル | CPUから見るとかなり遠い |
【図9-1】CPUから見たメモリ階層の距離感:
この差が大きいので、CPUは「速く計算すること」以上に「遅いデータ待ちを隠すこと」に多くの工夫を割いています。
キャッシュミス時にCPUでは何が起きるか
キャッシュミスは「遅い」だけで済ませると理解しにくいので、1本のロード命令がどう苦しくなるかを流れで見ると分かりやすいです。
- ロード命令を発行する
- L1を見る
- ないのでL2、L3、DRAMを順に探す
- その間、その値に依存する命令は待つ
- CPUは別に進められる命令があればOoOで前倒しする
- 前倒しできる仕事も尽きると、本格的に止まったように見える
つまり、キャッシュミスは単なる1回の待ちではなく、命令列全体の並び方に影響する待ち です。
TLB(Translation Lookaside Buffer)
仮想アドレスから物理アドレスへの変換を高速化するキャッシュ。
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とスレッド並列の違い:
| 観点 | SIMD | スレッド並列 |
|---|---|---|
| 並列化の場所 | 1コア内部 | 複数コア / 複数スレッド |
| 得意な処理 | 同じ演算の繰り返し | 独立タスクの分割 |
| 苦手な処理 | 分岐だらけ、要素ごとに挙動が違う | 共有資源競合、ロック待ち |
| 典型例 | 行列、画像、音声、暗号 | Webサーバ、ジョブ実行、並列ビルド |
x86-64 SIMD系統
SSE(Streaming SIMD Extensions)
SSE2-SSE4.2
- SSE2:128ビット整数演算
- SSE3-SSE4.2:より多くの命令セット
AVX(Advanced Vector Extensions)
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は、複数のコア(独立した実行ユニット)を持つ。
SMP(対称型マルチプロセッシング)
すべてのコアが等しい権限と機能を持つ。メモリアクセス遅延がほぼ同じ(UMA:Uniform Memory Access)。
- Intel Core iシリーズ(通常)
- AMD Ryzen 5000(非3D V-Cache版)
NUMA(非対称メモリアクセス)
メモリが複数のノードに分散し、ローカルなメモリアクセスは速いが、リモートアクセスは遅い。
SMT(Simultaneous MultiThreading)/ HyperThreading
1つのコア内で複数のスレッド(論理プロセッサ)を交互に実行。
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以降)
キャッシュコヒーレンシの管理
マルチコアでは複雑な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の場合)
マルチコアプログラミングの課題
共有メモリの競合(Race Condition)
// スレッド1
counter = counter + 1;
// スレッド2
counter = counter + 1;
// 期待値:counter = 2
// 実際:counter = 1(レースコンディション)
対策:ロック、アトミック操作、メモリフェンス
False Sharing
同じキャッシュラインに異なるスレッドのデータが載ると、キャッシュトラフィックが増加。
対策:パディング、スレッドローカルストレージ
浮動小数点とベクトル拡張
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
ベクトル拡張の詳細
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は自分の命令だけを見ていればよいわけではありません。外からの出来事に反応する必要があります。
割り込みの例:
- タイマ
- キーボード入力
- ディスクI/O完了
- ネットワーク到着
- ページフォルト
直感的には、作業中に急ぎの連絡が飛び込んでくる感じです。
割り込みvs例外
- 割り込み(Interrupt):外部からの非同期イベント(タイマ、I/O)
- 例外(Exception):実行中の命令から生じる同期イベント(ページフォルト、ゼロ除算)
割り込み処理のフロー
- 割り込み信号を受信
- 割り込みコントローラ(APICなど)がベクタ番号を決定
- 現在の文脈を退避
- PC(プログラムカウンタ)、フラグ、スタックポインタ等をスタックに保存
- 割り込みハンドラへジャンプ
- ベクタ番号に応じた割り込みハンドラのアドレスへ
- 割り込みハンドラ実行
- 割り込み原因に対応する処理
- 復帰
- 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つの実行環境を分離:
- Rich Execution Environment(REE):通常のOS(スマートフォンOSなど)
- 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)
対策
仮想化拡張
物理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:アプリケーション
EL1:OS(ゲスト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
仕様
用途
- 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)
- 企業向けサーバー向け
現在の動向
チップレット化の加速
例
- AMD Ryzen:複数のCXU(Core Complex Unit)を1つのダイに統合
- Intel Meteor Lake:複数のタイル(P-tile, E-tile, GPU tile)を1パッケージに統合
利点
- 製造不良率低下(各タイルの面積が小さい)
- 異なるプロセスで最適化(GPUは低周波、低電圧が可能)
- コスト低下
課題
- タイル間の通信遅延
- キャッシュコヒーレンシ複雑化
3Dメモリ積層の拡大
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の進化競争
- 省電力で高性能を両立
- スマートフォン、タブレット、サーバーへ進出
- 次世代ゲーム機候補
- 後方互換性を維持
- 既存ソフトウェア資産が膨大
- 仮想化サポートが強い
両者は徐々に特徴が収束。
RISC-Vの産業化進行
ただし現時点では、実装数はARM/x86-64に遠く及ばない。
性能の見方
CPUの性能はクロック周波数だけでは決まりません。
などが絡みます。
IPCとCPIの発想
CPU性能の見方では、
IPC: 1サイクルあたり何命令進むかCPI: 1命令あたり何サイクルかかるか
という考え方がよく出ます。クロックが高くてもIPCが低ければ伸びませんし、逆にクロックが少し低くても効率のよい実行で勝つことがあります。
性能 = IPC × クロック周波数
= (命令数 / CPI) × クロック周波数
CPUバウンドとメモリバウンド
- CPUバウンド: 計算そのものが支配的
- メモリバウンド: メモリ待ちが支配的
この違いを見ないまま最適化すると、頑張る場所を間違えます。CPUバウンドなら演算や分岐を見直し、メモリバウンドならデータ配置や局所性を見るほうが効きます。
性能観察の入口
レイテンシとスループット
- レイテンシ: 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倍高速)
ここでは、
が重なっています。
ケース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をまたぐメモリアクセスが多い
- 実際にはメモリ帯域が先に飽和している
ここでCPUの知識が効くのは、「コアが忙しい」のと「仕事が前に進んでいる」のは別だと分かることです。
発展ルート
- CPUマイクロアーキテクチャ
- 分岐予測器
- Out-of-Order実行
- GPU / アクセラレータとの比較
それぞれ何を深めるか
- CPUマイクロアーキテクチャ: 実行ユニット、リネーミング、リオーダバッファなど内部構造を見る
- 分岐予測器: 予測の当たり方が性能へどう効くかを学ぶ
- Out-of-Order実行: プログラム順と実行順のズレをどう整えるかを見る
- GPU / アクセラレータとの比較: CPUが得意な仕事と、別ハードウェアが得意な仕事を見分ける
ミニ比較表
| 概念 | 何を良くするか | よくある誤解 |
|---|---|---|
| パイプライン | スループット | 1命令のレイテンシを必ず下げると思う |
| 分岐予測 | 停止を減らす | 分岐そのものを消していると思う |
| SIMD | 同種データの一括処理 | スレッド並列と同じだと思う |
| ISA | 外から見える命令の約束 | 内部実装そのものだと思う |
| Out-of-Order実行 | 依存のない命令を前倒し | プログラム順を壊してよいと思う |
| 3D V-Cache | キャッシュ容量 | 遅延が短くなると思う |
| SMT | 未使用リソース活用 | すべてのワークロードで高速化すると思う |
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を学ぶ順番
- 命令実行の基本(Fetch-Decode-Execute)
- レジスタとISA(x86-64, ARM64, RISC-Vの違い)
- パイプライン(5段~深いパイプライン)
- 分岐予測とOut-of-Order実行
- SIMD(SSE, AVX, NEON, SVE)
- 割り込み、特権モード、システムコール
- マルチコア、キャッシュコヒーレンシ
- セキュリティ拡張、仮想化
最初から最適化の細部へ行くより、「何がどこで詰まるか」という観点で積むと理解しやすいです。
どこでメモリの話と合流するか
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等)が選ばれることもあります。
次に読むなら
補足
第5章 コンピュータアーキテクチャ
プログラムが動く「物理的な舞台」を見る章です。CPU・メモリ・ストレージの速度差、キャッシュ、ISA、パイプラインといった概念を押さえ、「なぜ同じコードでもハードウェアによって速度が変わるのか」がわかるようになります。
コンピュータは一枚岩ではなく、速度も役割も違う部品の組み合わせです。本章ではCPU・キャッシュ・メモリの関係から性能の直感を作ります。
この章が実務で役立つ場面
5.1 CPU・メモリ・ストレージ
プログラムはCPUが実行し、命令やデータはメモリに置かれ、長期保存はストレージに行います。
【図10】ストレージ・メモリ・CPUのデータの流れ:
5.2命令実行の基本
CPUは大ざっぱに言うと、
- 命令を読む
- 解釈する
- 実行する
を繰り返します。これを fetch-decode-execute サイクルと呼びます。
5.2.1割り込みはなぜ必要か
CPUがずっと自分の命令だけを淡々と実行していると、外からの出来事にうまく反応できません。そこで使われるのが 割り込み です。
割り込みは、
- キーボード入力が来た
- ディスクI/Oが終わった
- タイマが発火した
といった出来事をCPUに知らせる仕組みです。
直感的には、「いまの作業を少し中断して、急ぎの連絡に対応する」感じです。OSのスケジューリングやI/O完了通知は、この仕組みの上に成り立っています。
5.3キャッシュ
CPUとメモリには速度差があるため、途中にキャッシュが入ります。
- L1: 非常に速いが小さい
- L2 / L3: 少し遅いが大きい
データが近くにまとまっていると速くなりやすい、というのが重要な直感です。
【図30】メモリ階層と速度(2025年の典型値):
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命令で複数データを処理する機構:
画像処理、機械学習、暗号化、圧縮で劇的な高速化。numpy の内部やLLMの行列演算はすべてSIMDを駆使している。
5.5レジスタ
レジスタはCPU内にある極めて高速で小さな記憶領域です。計算中の値やアドレス、制御情報を保持します。
5.6命令セットアーキテクチャ
CPUごとに理解できる命令の集合があり、これをISAと呼びます。
同じ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が理解する命令セットが異なるからです。
CPU仕様と性能資料の補足
Agner Fog の CPU オプティマイザーズ マニュアル
CPUパイプラインと最適化: agner.org(https://agner.org/)が提供するCPU最適化に関する資料は、プロセッサ設計の内部仕様と高性能コード作成の実践知を統合した、業界で最も詳細な参考資料です。以下の内容が含まれています:
- マイクロアーキテクチャ: Intel Skylake, Alder Lake, AMD Zen3/Zen4における命令パイプライン、キャッシュ構造、分岐予測の詳細
- 命令スケジューリング: 命令レベルの並列性(ILP)を最大化するコード配置
- メモリ最適化: キャッシュミスの最小化、プリフェッチングの活用
- ベクトル化: SIMD命令(SSE, AVX, AVX-512)による並列処理
定量的なパフォーマンスデータ: 単なる理論ではなく、具体的なマイクロプロセッサについて、「この命令は何サイクルか」「このメモリアクセスは何ナノセカンドか」といった実測データが提供されており、最適化の意思決定に直結した情報です。
版管理: Agner Fogのマニュアルは、新しいCPU世代(Intel Ice Lake, AMD Genoa等)が発表されるたびに更新されており、最新のマイクロアーキテクチャ情報を追跡できます。
Intel 公式ドキュメント
Intel 64 ISA (Instruction Set Architecture): Intel.com(https://intel.com/)が提供する公式仕様には、以下のコアドキュメントが含まれています:
- Intel 64 and IA-32 Architectures Developer’s Manual: x86/x64プロセッサの完全な命令セット定義(3000ページ以上)
- Software Developer’s Manual: アセンブリプログラミングの実務指南
- System Programming Guide: メモリ管理、割り込み処理、仮想化機能
AVX-512と拡張機能: Intel Xeon、Intel Core Ultra等における最新のSIMD拡張(AVX-512 VNNI等)の正式定義が提供されており、データセンター向けの高性能コンピューティング実装の基盤となっています。
仮想化とセキュリティ: Intel VT-x(仮想化拡張), TPM(Trusted Platform Module), SGX(Software Guard Extensions)等のセキュリティ機能の仕様も記載されています。
ARM Processor Documentation
ARM ISA (Instruction Set Architecture): developer.arm.com(https://developer.arm.com/)が公開するARM 64-bit(ARMv8以降)の仕様は、以下の領域をカバーしています:
- ARMv9仕様: 2021年リリースの新命令セット、SVE(Scalable Vector Extension)
- A-profile(Application): 汎用プロセッサ向けの高性能命令
- M-profile(Microcontroller): マイコン向けの低消費電力設計
- システムコール: OSカーネルとの境界定義
モバイルと組み込み: ARM Cortex-A, Cortex-M, Cortex-Rシリーズの詳細仕様により、スマートフォン、IoTデバイス、自動車ECUなどの設計が可能になります。
パフォーマンス予測: ARM公式ドキュメントには、「このコード配置でこのサイクル数」といった予測モデルが含まれており、アセンブリ最適化の指針となります。
RISC-V Specification
オープンISAの標準化: riscv.org(https://riscv.org/)が管理するRISC-V(Reduced Instruction Set Computer)は、オープンソースのプロセッサISAです。以下の特徴があります:
- モジュラー設計: Base ISA (RV32I/RV64I)に、オプション拡張(M, A, F, D, C等)を組み合わせ可能
- ライセンス無料: ISA仕様の使用に特許ロイヤリティ不要
- 実装の透明性: RISC-V CPUのオープンソース実装(BOOM, CVA6等)が利用可能
- 学習リソース: アカデミア向けの教育用ISA
実装の多様性: Google, SiFive, Alibaba等が商用RISC-VプロセッサやSoC(System on Chip)を開発しており、組み込みからハイエンドまでのスペクトラムをカバーしています。
Intel MCA (Memory Checker Architecture) と障害検出
キャッシュコヒーレンスとメモリモデル: Intel CPUドキュメントに含まれるMemory Consistency Modelの仕様は、マルチスレッドプログラミングの基盤です。以下の概念が定義されています:
- メモリバリア: ロード・ストア操作の順序付け(MFENCE, LFENCE, SFENCE)
- キャッシュラインの粒度: 64バイト(Intelの一般的な粒度)でのコヒーレンス
- メモリの「可視性」: 一つのスレッドのメモリ書き込みが他のスレッドから見える時点
WikiChip - プロセッサデータベース
プロセッサ仕様の集約: en.wikichip.org(https://en.wikichip.org/)は、数千のプロセッサについて以下の情報を集約したデータベースです:
- Microarchitecture: コアカウント、キャッシュサイズ、メモリインターフェース
- Performance: SPEC CPU スコア、消費電力(TDP)
- Instruction Set: サポートされているISAと拡張機能
- Timeline: 発売日、廃止日、世代進化の履歴
比較表: 複数のプロセッサを並べて仕様を比較でき、「Intel Core i9-13900K vs AMD Ryzen 9 7950X」といった実務的な選定判断に活用できます。
FreeBSD Performance Analysis
Operating System視点の最適化: people.freebsd.org(https://people.freebsd.org/)には、FreeBSDオペレーティングシステムの観点からのCPUパフォーマンス分析資料が掲載されています。以下の内容が含まれます:
- System Call Overhead: ユーザーモード/カーネルモード切り替えのコスト
- Context Switching: プロセス/スレッド間の切り替え効率
- CPU Scheduling: OSがスレッドをCPUコアに割り当てるアルゴリズム
ボトルネック分析: これらの資料により、「CPUの計算理論的な限界」だけでなく、「OSレベルでの実装オーバーヘッド」を定量的に理解できます。
パイプライン最適化と命令レベル並列性
スーパースカラアーキテクチャ: 現代 CPU は複数の命令を同時に実行できるスーパースカラ設計を採用しています。最適化を考えるときは、以下の観点が重要になります:
- 命令スケジューリング: コンパイラが命令の順序を最適化し、依存性スタールを回避
- ループアンローリング: ループボディを展開して依存性を減らす
- データキャッシング: ホットなデータをレジスタ・キャッシュに保持
分岐予測とパイプラインフラッシュ: CPU は条件分岐の結果を予測的に実行(speculative execution)しますが、予測が外れるとパイプライン全体をフラッシュして再実行する大きなペナルティが発生します。条件分岐が多い処理では、予測が外れやすい分岐を測定し、データ配置や分岐条件を見直します。
キャッシュ階層とメモリアクセスパターン
L1/L2/L3 キャッシュ: 階層的なキャッシュ構造では、CPUに近いほど容量は小さく、アクセスは速くなります。典型的には次のような差があります:
- L1キャッシュ: 4 クロック(32KB per core、Skylake)
- L2キャッシュ: 12 クロック(256KB per core)
- L3キャッシュ: 42 クロック(共有、8MB Skylake)
- メインメモリ: 100+ クロック(100+ nanoseconds)
このレイテンシー差を理解することで、「キャッシュ効率的なコード」の設計が可能になります。
False Sharing: マルチスレッドプログラムでは、同じキャッシュライン(64バイト)上の異なるメモリを複数スレッドが更新すると、キャッシュラインの奪い合い(contention)が発生します。スレッドごとのデータを分離し、頻繁に更新される値を同じキャッシュラインに詰め込まないことが重要です。
AMDアーキテクチャの特性
Zen3 と Zen4: AMD Ryzen 9000 シリーズ(Zen5) は、Agner Fog によるベンチマーク結果がまだ完全には公開されていませんが、以下の傾向があります:
- L3キャッシュの構造: AMD は Zen3 で 8コア共有 L3 を採用し、Intel より高いスループット
- メモリコントローラ統合: Infinity Fabric により複数ダイ間の低遅延通信
- クロック周波数: Ryzen 9 での boost clock は 5.5+ GHz(Intel より高い傾向)
VPU (Vector Processing Unit) と SIMD
AVX-512 と 512ビット SIMD: Intel Xeon では AVX-512 により 512ビット(8個の64ビット整数、または 16個の32ビット単精度浮動小数点数)を同時処理できます。以下の命令群がサポートされています:
- VNNI (Vector Neural Network Instructions): 行列積和(fused multiply-add)を高速化、AI ワークロード向け
- Prefetch and Gather: メモリアクセスパターンの最適化
ARM SVE (Scalable Vector Extension): ARM64 アーキテクチャの拡張であり、ベクトル長がスケーラブル(128~2048ビット)な点で AVX-512 と異なります。これにより、同じコードが異なる SVE 実装幅で動作します。
パフォーマンスカウンタと プロファイリング
CPU パフォーマンスカウンタ: Linux の perf, macOS の Instruments, Windows の VTune により、CPU の内部イベント(キャッシュミス、分岐予測失敗、メモリレイテンシ等)をプロファイルできます。実行時間だけでなく、どのハードウェアイベントが増えているかを見ることで、最適化ポイントが明確になります。
プロセッサ世代の進化
Intel プロセッサ名称体系: Intel は以下のコード名で世代を管理しています:
- Haswell (2013)
- Broadwell (2014)
- Skylake (2015)
- Kaby Lake (2016)
- Coffee Lake (2017)
- Ice Lake (2019)
- Tiger Lake (2020)
- Alder Lake (2021)
- Raptor Lake (2022)
- Meteor Lake (2023)
各世代は、微細化プロセス、キャッシュ構造、SIMD拡張、省電力制御、セキュリティ緩和策などを変えながら進化します。同じコードでも世代やベンダーが変わるとボトルネックが変わるため、最適化は対象環境を明確にして行います。
まとめ
CPUは、命令をどう読み、どう並べ、どう速くするかを理解するための中心的な実行基盤です。ISA、パイプライン、分岐予測、SIMD、メモリ階層、パフォーマンスカウンタをつなげて見ると、性能の見方が具体的になります。最適化では推測より測定を優先し、キャッシュ、分岐、並列性のどこで詰まっているかを確認することが重要です。
参考文献
講義・記事
- Intel 64 and IA-32 Architectures Software Developer Manuals
- Intel 64 and IA-32 Architectures Software Developer’s Manual
- What Every Programmer Should Know About Memory
書籍
- Computer Systems: A Programmer’s Perspective
- Operating Systems: Three Easy Pieces (OSTEP)
- Brendan Gregg - Systems Performance
- Patterson & Hennessy - Computer Architecture (RISC-V Edition)