CPUが速くても、データが届かなければ意味がない

前章(第3章)では、パイプライン化・スーパースカラ実行・アウトオブオーダー実行といった仕組みによって、 CPUが1サイクルあたりにいかに多くの命令を処理しようとしているかを見てきました。 しかしどれだけ演算ユニットが高速でも、処理すべき命令やデータがメモリから届かなければCPUは何もできず、ただ待つだけです。

この「CPUの処理速度」と「メモリのアクセス速度」の間には、埋めがたいギャップが存在します。 これは一般に「メモリの壁(Memory Wall)」と呼ばれ、現代のCPUアーキテクチャ設計における最大の課題の一つです。 本章では、このギャップをいかにして埋めるか——キャッシュ階層とメモリシステムの設計思想を掘り下げていきます。

メモリ階層の設計思想 — 速度・容量・コストのトレードオフ

メモリ階層(Memory Hierarchy)が存在する理由は、突き詰めれば一つです。プロセッサの処理速度とメモリのアクセス速度の間に大きなギャップがあるため、 そのギャップを一気に埋めようとすると、コストが天文学的に膨れ上がってしまうという事実です。

この問題への解決策として採用されているのが、CPUに近いほど高速・高コスト・小容量、 遠いほど低速・低コスト・大容量という階層構造です。レジスタを頂点に、L1キャッシュ、L2キャッシュ、L3キャッシュ、 そしてメインメモリ(RAM)、最後にSSD/HDDといったストレージへと続くこのピラミッド構造によって、 性能とコストのバランスを現実的な範囲に収めています。

graph TD
  A[レジスタ<br/>数百バイト・1サイクル未満] --> B[L1キャッシュ<br/>16KB〜128KB/コア・1〜3サイクル]
  B --> C[L2キャッシュ<br/>256KB〜1MB/コア・4〜10サイクル]
  C --> D[L3キャッシュ<br/>2MB〜32MB以上・20〜40サイクル]
  D --> E[RAM メインメモリ<br/>GB単位・100サイクル以上]
  E --> F[ストレージ SSD/HDD<br/>TB単位・数万サイクル相当]

  style A fill:#3b82f6,stroke:#1d4ed8,color:#fff
  style B fill:#8b5cf6,stroke:#6d28d9,color:#fff
  style C fill:#8b5cf6,stroke:#6d28d9,color:#fff
  style D fill:#8b5cf6,stroke:#6d28d9,color:#fff
  style E fill:#f97316,stroke:#ea580c,color:#fff
  style F fill:#64748b,stroke:#475569,color:#fff
メモリ階層のピラミッド構造。上に行くほど高速・高コスト・小容量、下に行くほど低速・低コスト・大容量になる

この階層設計がうまく機能する理論的な根拠が「局所性の原理(Principle of Locality)」です。 局所性には2種類あります。

この2つの性質があるからこそ、「よく使うデータだけを高速な小容量メモリに置いておく」というキャッシュの戦略が成立します。 もしプログラムのメモリアクセスが完全にランダムであれば、キャッシュはほとんど役に立たなかったでしょう。

L1/L2/L3キャッシュの容量とレイテンシの目安

実際のキャッシュ階層は、レベルごとに容量とレイテンシ(アクセスにかかるサイクル数)が大きく異なります。 以下は一般的な現代CPUにおけるおおよその目安です(世代・製品によって幅があります)。

レベル容量目安レイテンシ目安共有範囲
L116KB〜128KB/コア1〜3サイクルコア専用
L2256KB〜1MB/コア4〜10サイクルコア専用または一部共有
L32MB〜32MB以上20〜40サイクル複数コアで共有
RAMGB単位100サイクル以上全コア共有

L1からL3、そしてRAMへと進むにつれてレイテンシが桁違いに増えていくことがわかります。 L1のヒット(1〜3サイクル)とRAMアクセス(100サイクル以上)の差は、実に数十倍から百倍近くにも及びます。 この差こそが、「いかにキャッシュにデータをヒットさせ続けるか」がCPU性能に直結する理由です。

キャッシュライン — なぜ1バイトではなく64バイト単位で読み込むのか

空間的局所性を最大限に活用するため、キャッシュは必要なデータを1ワードずつではなく、 連続した複数ワードからなる「ブロック」(一般に「キャッシュライン」と呼ばれる)単位でメインメモリから読み込みます。 キャッシュラインのサイズは一般的に64バイト以上です。

つまりキャッシュミスが発生した際、CPUは要求された1つのワードだけでなく、その前後を含む64バイト分のブロックをまとめてフェッチします。 空間的局所性の原理に従えば、隣接するデータも近いうちにアクセスされる可能性が高いため、 「ついでに読んでおく」ことでその後のアクセスをキャッシュヒットに変えられるという発想です。

キャッシュミスの4分類

キャッシュが「外れる」瞬間、つまりキャッシュミスには、発生原因によって4つの分類があります。

分類英語名発生原因
初回ミスCompulsory miss初めてアクセスするデータのため、キャッシュに存在せず不可避に発生するコールドミス
容量ミスCapacity missワーキングセット(処理中に使うデータ全体)に対してキャッシュ容量が不足している
競合ミスConflict miss連想度(同じインデックスに格納できるライン数)不足により、必要なラインが他のラインとの競合で追い出される
一貫性ミスCoherence missマルチコア環境でキャッシュの一貫性を維持する過程で発生するミス

Compulsory missはどれだけキャッシュ設計を工夫しても避けられない「初回だから仕方がない」ミスです。 一方でCapacity missとConflict missはキャッシュサイズや連想度の設計次第で減らせる余地があり、 Coherence missは次節で扱うマルチコア特有の問題です。

キャッシュコヒーレンシ — MESIプロトコルによる一貫性の維持

現代のCPUはほぼ例外なくマルチコアです。各コアがそれぞれ自分専用のL1/L2キャッシュを持っている場合、同じメモリアドレスのデータが複数のコアのキャッシュに別々のコピーとして存在しうるという問題が生じます。 あるコアがそのコピーを書き換えたとき、他のコアが古い値を見続けてしまっては困ります。 この整合性を保つ仕組みがキャッシュコヒーレンシ(Cache Coherence)であり、 その代表的な実装がMESIプロトコルです。

MESIは、各キャッシュラインの状態を4つの頭文字が示す状態のいずれかで管理します。

あるコアがShared状態のラインに書き込みを行おうとすると、他のコアが持つ同じラインのコピーを無効化(invalidate)し、 自分のラインをModifiedへ遷移させます。こうすることで「複数のコアが同じデータについて異なる値を正としてしまう」事態を防ぎます。

この無効化を実現する仕組みがバススヌーピング(Bus Snooping)です。 各コアのキャッシュコントローラは、共有バス上を流れる他コアのメモリアクセスを常に監視(スヌープ)しており、 自分が保持しているラインに関係する書き込みが発生した場合は、即座に自分のラインをInvalidへ遷移させます。 この常時監視の仕組みがあるからこそ、前節で触れた「Coherence miss」が発生します—— 自分では何も変更していなくても、他コアの書き込みによって自分のキャッシュラインが無効化されてしまうのです。

キャッシュメモリの歴史 — 「メモリの壁」への対応の歩み

キャッシュメモリという概念自体は古くからありますが、CPUチップとの統合という観点では、 1980年代にCPUの動作速度がメインメモリのアクセス速度を大きく上回るようになったことが直接のきっかけです。 この速度差を埋める緩衝材として、キャッシュメモリの重要性が急速に高まりました。

1995年頃に登場したIntelのP6マイクロアーキテクチャは、L2キャッシュをCPUと同一クロックで動作させる形でチップに統合した先駆けとして知られています。 さらに2003年、コンシューマ向けCPUとしてPentium 4 Extreme Editionが2MBのL3キャッシュを搭載しました (なお、この「コンシューマ向けとして初めてL3を搭載した」という表現の厳密性については情報源によって幅があり、一次資料での要検証事項として付記しておきます)。 CPU黎明期からマルチコア化に至るまでの詳しい経緯は第1章・第5章で扱った通りですが、 キャッシュ階層の進化はこの「メモリの壁」への対応の歴史そのものだったといえます。

RAM・ストレージ・スワップ — メモリ階層の下層を理解する

キャッシュ階層の下には、メインメモリ(RAM)とストレージ(SSD/NVMe/HDD)が控えています。 それぞれの性質を正しく理解しておくと、PCが「重くなる」理由も見えてきます。

RAMとストレージの違い

RAMは、CPUが現在処理中のデータを一時的に保持する揮発性メモリです。電源を切ると内容はすべて消えます。 一方ストレージ(SSD/NVMe/HDD)は、電源を切っても内容が保持される不揮発性の長期保存デバイスです。

スワップとページフォールト — なぜPCは「重く」なるのか

RAMの容量には物理的な上限があります。実行中のプログラムが必要とするメモリ量がRAMの容量を超えそうになると、 OSは使用頻度の低いメモリ内容(ページ)をディスク上の特別な領域「スワップ領域」に一時的に退避させます。 この仕組みをスワップ(ページング)と呼びます。

問題は、スワップ領域への退避・復帰がディスクI/Oを伴う点です。RAMへのアクセスが数十〜数百ナノ秒オーダーであるのに対し、 ディスクI/Oはその比ではないほど低速です。スワップが多発する状態はスラッシング(Thrashing)と呼ばれ、 CPU自体は何も悪くないのに、システム全体の体感速度が著しく悪化するという現象を引き起こします。 「ブラウザのタブを開きすぎるとMacが重くなる」体験の正体は、多くの場合このスラッシングです。

この過程で発生するのがページフォールト(Page Fault)です。 ページフォールトとは、プログラムが必要とするデータがRAM上に存在しない際に発生する例外的な中断イベントであり、 故障やエラーではなく正常な仮想メモリ管理の一部です。OSはページフォールトを検知すると、 必要なページをディスク(あるいはスワップ領域)からRAMへ読み込み直してから処理を再開します。

メモリ帯域幅の計算式

最後に、RAMの性能を語る上でもう一つ重要な指標であるメモリ帯域幅(Memory Bandwidth)を見ておきましょう。 帯域幅は次の式でおおよそ計算できます。

ただし、この計算式はあくまで理論上の最大値です。実際の使用環境では、メモリコントローラのオーバーヘッドや アクセスパターンの非効率性などにより、実測値は理論最大値の70〜90%程度に留まることが多いとされています。 カタログスペックの帯域幅数値を鵜呑みにせず、あくまで理論上の上限として捉えておくのが実務上は安全です。

なお、こうしたRAMとCPUの関係をさらに一歩進め、CPU・GPU・メモリを一つのチップ上に統合し、 同じメモリ空間を共有する設計思想がUMA(統合メモリアーキテクチャ、Unified Memory Architecture)です。 Apple SiliconのM1以降のチップが採用しているこの設計は、メモリ階層のあり方そのものを見直す試みであり、 詳しくは第7章のヘテロジニアスコンピューティングで扱います。

まとめ — メモリの壁とどう向き合うか

本章では、CPUとメモリの速度差という「メモリの壁」を出発点に、局所性の原理に基づくキャッシュ階層の設計、 キャッシュラインとミスの分類、マルチコア環境での一貫性を保つMESIプロトコル、 そしてRAM・ストレージ・スワップという下位層までを辿ってきました。

次章(第5章)では、視点を変えてCPUの歴史における大きな転換点——なぜシングルコアの周波数競争が限界に達し、 マルチコア化へと舵が切られたのかを見ていきます。

理解度チェック

問題 0 / 50%
Q1

メモリ階層の設計が「局所性の原理」に支えられているとされる理由として、最も適切なものはどれですか?

キーボード: 1〜4 で選択、Enter で回答