キャッシュメモリとは、メモリの階層と速さの差

回 3-3 道 3 コンピュータの中

キャッシュメモリは、CPU と主記憶 (メインメモリ) の間に置く、小さくて速い記憶の置き場です。最近使ったデータとその近くのデータを行 (ライン) という塊で控えておき、次に同じ所を読むときは主記憶まで行かずに済ませます。Norvig の表では L1 キャッシュから読むのに 0.5 ナノ秒、主記憶から読むのに 100 ナノ秒で、当たるかどうかで 200 倍の差があります。

読む速さの差がどこで生まれ、どんな順に読めば当たるのかを切り分けられるようになります。

なぜ CPU はデータを待つのか

キャッシュメモリは、速い CPU が遅い主記憶を待たずに済むように、よく使うデータを CPU の近くに控えておく小さな置き場です。

8 行 8 列の表に入った 64 個の数を、全部足すとします。1 行目を左から右へ読み、次に 2 行目へ進む読み方と、1 列目を上から下へ読み、次に 2 列目へ進む読み方があります。足し算の回数は同じでも、読む順番で速さが変わることがあります。

理由は、計算をする CPU と、データを置く主記憶 (メインメモリ、作業中のデータを広げておく机) の間に、速さの大きな差があるからです。

メモリは作業中だけ広げる机で、保存する物は引き出しの SSD に入れる図

Red Hat の Ulrich Drepper は、2007 年の論文の冒頭で、多くのプログラムの速さを縛っているのは今やメモリを読む速さだと書いています。そこで CPU の近くに、小さくて速い控えの置き場を作りました。これがキャッシュメモリです。この回の終わりには、上の 2 つの読み方のどちらがなぜ速いのかを、数で説明できるようになります。

メモリの階層とキャッシュの仕組み

記憶の置き場は、速くて小さい物から遅くて大きい物へ、段のように並んでいます。CPU はまず一番近い段を見て、無ければ 1 段ずつ下へ取りに行きます。

速い置き場ほど小さい

CPU に近い段から順に、レジスタ (CPU の中で計算中の値を持つ所)、L1 キャッシュ、L2 キャッシュ、L3 キャッシュ、主記憶、ディスクと並びます。L は Level (段) の頭文字で、L1 が一番 CPU に近く、一番速くて一番小さい段です。

読むのにかかる時間の目安を、2 つの一次資料から並べます。Norvig の表は「よくある PC」の目安をナノ秒 (10 億分の 1 秒) で書いた物です。Drepper の表は、Intel が Pentium M という古い CPU について示した数を、CPU の 1 拍 (サイクル) の数で書いた物です。

段Norvig の表 (ナノ秒)Drepper の表 (サイクル、Pentium M)
レジスタ載っていない1 以下
L1 キャッシュ0.5約 3
L2 キャッシュ7約 14
L3 キャッシュ載っていない載っていない
主記憶100約 240
ディスク (読む場所を移す)8,000,000載っていない

どちらの表でも、主記憶は L1 より 2 桁ほど遅く、ディスクは主記憶よりさらに何桁も遅くなります。今の CPU の値はこれと同じではありませんが、段が下がるごとに桁で遅くなる形は同じです。

下の図で扱うデータを大きくすると、置き場が下の段へ落ちていきます。図の時間は桁の感じをつかむための目安で、上の表の値とは別物です。

速い置き場ほど小さい

速い段を大きく作らないのは、値段のためです。キャッシュには SRAM (電気を流している間、値を保ち続ける記憶の部品)、主記憶には DRAM (小さな電気の溜まりで値を持ち、ときどき入れ直す部品) を使います。Drepper は、すべてを SRAM にしない理由は費用で、SRAM はずっと高いと書いています。

局所性、近い所と最近の所はまた使われる

キャッシュが役に立つのは、プログラムの読み方に局所性 (使う場所が近くにかたまる性質) があるからです。局所性には 2 つあります。

種類意味よくある場面
時間の局所性最近使った物は、またすぐ使うループの中で同じ変数や同じ関数を何度も使う
空間の局所性使った物のすぐ近くも、すぐ使う配列を先頭から順に読む

キャッシュは、この 2 つの性質に賭けた仕組みです。

当たりと外れ、行の単位で載せる

CPU が読みたいデータがキャッシュにあることを当たり (ヒット)、無いことを外れ (ミス) と呼びます。当たれば近くの段からすぐ渡せます。外れれば下の段まで取りに行き、取ってきた物をキャッシュに置いてから渡します。

キャッシュは、1 つの数だけを取ってくるのではなく、そのまわりを含む行 (ライン、まとめて運ぶ塊) を単位にして運びます。Drepper によると、昔の行は 32 バイトで、2007 年の時点では 64 バイトが普通でした。1 つ目の数で外れても、同じ行に入っている隣の数は当たりになります。空間の局所性を活かす作りです。

いっぱいのときと、書いたとき

キャッシュは小さいので、すぐいっぱいになります。新しい行を置くには、どれかを追い出します。Drepper は、多くのキャッシュは一番長く使われていない物 (LRU、Least Recently Used) から追い出すと書いています。時間の局所性があるなら、長く使っていない物ほど、この先も使われにくいからです。

書き込みにも 2 つのやり方があります。

やり方書いたとき良い所と弱い所
ライトスルーすぐ主記憶にも書く作りは簡単。同じ所を何度も書くと、主記憶への書き込みが増える
ライトバックキャッシュの行に印を付けるだけ。追い出すときに主記憶へ書く速い。Drepper は、まともな CPU の載った機械では、多くのメモリをこちらで扱うと書いている

主記憶の下の段、仮想メモリとページ

主記憶の下にも段があります。仮想メモリ (プログラムに、本当より広い番地の空間を見せる OS の仕組み) は、番地をページ (決まった大きさの区画) に分け、使っている所だけを主記憶に載せます。載っていない所を触ると、OS がディスクから運んできます。

考え方はキャッシュと同じですが、ディスクは主記憶より何桁も遅いので、ページの出し入れが続くと、プログラム全体が目に見えて遅くなります。下の図は 1 ページを 4KB として、触ったページの数と追い出しの回数を数えます。

要る所だけ載せる

動かして確かめる

キャッシュの模型を Python で作り、表の読み方で当たりの数がどう変わるかを数えます。模型の決まりは次の通りです。

  • 番地は 0 から始まる数で、8 つずつが 1 つの行になる (0〜7 が行 0、8〜15 が行 1)
  • キャッシュに置ける行は 4 本で、どの行もどこにでも置ける
  • いっぱいなら、一番長く使っていない行を追い出す (LRU)

行の大きさも本数も、本物の CPU より小さくした模型です。

1 回ずつ追ってみる

まず、読んだ番地ごとに、当たりか外れかを 1 行ずつ出します。

動かす 1 回ずつ当たりと外れを見る

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

0 0 外れ
1 0 当たり
2 0 当たり
9 1 外れ
17 2 外れ
25 3 外れ
3 0 当たり
33 4 外れ
  出す 1
9 1 外れ
  出す 2

各行は、読んだ番地、行の番号、結果です。「出す」の後ろは追い出した行の番号です。

番地 0 で外れると、行 0 (番地 0〜7) がまとめて載るので、続く 1 と 2 は当たりです。これが空間の局所性です。番地 3 は 4 回前に読んだ行 0 に入るので当たり、時間の局所性です。番地 33 のとき 4 本が埋まっていて、一番長く使っていない行 1 を追い出しました。行 0 は直前に番地 3 で使ったので残っています。最後に番地 9 を読み直すと、行 1 はもう追い出されているので外れです。

表を行の順と列の順に読む

次に、冒頭の 8 行 8 列の表を 2 つの順に読み、64 回のうち何回当たるかを数えます。表は 1 行目の 8 個、2 行目の 8 個と、行の順に番地を並べて置いてあるとします。

動かす 読む順で当たりの数を比べる

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

行の順 56
列の順 0

行の順では 64 回のうち 56 回当たりました。外れは各行の最初の 1 回ずつで、1 つ外れるたびに 8 個が載り、残りの 7 個が当たります。

列の順では 1 回も当たりません。0 になったのには 2 つの条件がそろっています。1 つは、次に読む番地が 8 つ先で、行の大きさと同じだけ飛ぶので、毎回別の行を読むことです。もう 1 つは、1 列を読み終えるまでに 8 本の行を触るのに、キャッシュには 4 本しか置けないことです。2 列目に移って行 0 に戻るころには、行 0 はもう追い出されています。

平均でどれだけ待つか

当たりの割合 (ヒット率) から、1 回読むのに平均で何ナノ秒待つかを見積もります。見積もりの置き方は次の通りです。

  • 当たりは L1 から読むとして、Norvig の表の 0.5 ナノ秒
  • 外れは主記憶から読むとして、Norvig の表の 100 ナノ秒 (L2 と L3 は考えない)
  • 当たりの数は、上の模型の 56 回と 0 回 (どちらも 64 回中)

動かす 平均の待ち時間を見積もる

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

行の順 12.9375
列の順 100.0
差の倍率 200.0

行の順なら 1 回あたり約 13 ナノ秒、列の順なら 100 ナノ秒です。1 回ずつの差が 200 倍あるので、ヒット率が少し下がるだけで平均は大きく動きます。模型の上の見積もりで、測った時間ではありません。

キャッシュが効かなくなるのはいつか

キャッシュは「近い所と最近の所をまた使う」という前提に賭けています。前提が崩れると、キャッシュがあっても毎回下の段まで取りに行くことになります。

使う範囲がキャッシュより大きい

何本かの行をくり返し読むとします。読む行の本数がキャッシュに置ける本数を超えたとき、何が起きるかを数えます。

動かす 置ける本数を 1 本超えると

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

3 本を 10 周 27 / 30
4 本を 10 周 36 / 40
5 本を 10 周 0 / 50
8 本を 10 周 0 / 80

3 本と 4 本では、外れは 1 周目の最初の数回だけで、2 周目からは全部当たります。ところが 5 本にすると、当たりが 0 になりました。4 本しか置けないところに 5 本目を載せると、一番長く使っていない行、つまり次にすぐ使う行を追い出してしまうからです。追い出した行を次に読むときにはまた外れ、その行を載せるためにまた次の行を追い出す、が続きます。

1 本増えただけで、9 割当たっていたのが 0 になりました。この 0 は、どの行もどこにでも置けて LRU で追い出す、この模型での結果です。本物のキャッシュは置き場所の決め方や追い出し方が違うので、同じ数にはなりません。

崩れる前提をまとめると、次の通りです。

  • 使う範囲はキャッシュに収まる、という前提。超えると、LRU では次に使う物から追い出すことがある
  • 次に読むのは近くの番地、という前提。行の大きさ以上に飛んで読むと、行にまとめて載せた残りが無駄になる (列の順の 0)
  • 主記憶に全部が載る、という前提。載らないとページの出し入れが始まり、ディスクの遅さがそのまま効く

仕事でどう確かめるか

仕事では、まず自分の機械のキャッシュの値を調べ、次に読む順番を疑い、最後に直す前と後の速さを同じ条件で比べます。

1 つ目は、行の大きさを決め打ちしないことです。Drepper の 2007 年の資料は 64 バイトを普通としていますが、手元の機械が答える値は違うことがあります。下は、Apple M3 Pro の Mac (macOS 26.3.1) で sysctl を動かした結果です。

手元の Mac で sysctl を動かした結果

$ sysctl hw.cachelinesize
hw.cachelinesize: 128
$ sysctl hw.l1dcachesize
hw.l1dcachesize: 65536
$ sysctl hw.l2cachesize
hw.l2cachesize: 4194304
$ sysctl hw.pagesize
hw.pagesize: 16384

この Mac は、行の大きさを 128 バイト、L1 のデータ用のキャッシュを 65536 バイト、L2 を 4194304 バイト、ページを 16384 バイトと答えました。ページの図は 4KB で描いていますが、この Mac のページはその 4 倍です。

2 つ目は、大きな表を回すループで、読む順番が置かれた順番と合っているかを見ることです。2 重のループの内と外を入れ替えるだけで、列の順が行の順になります。

3 つ目は、直す前と後の時間を、同じデータと同じ機械で並べて比べることです。模型は考える助けで、本物の速さは測って確かめます。

関わる実例

キャッシュの当たりと外れの時間の差は、速さの話だけでなく、守りの穴にもなりました。

Meltdown と Spectre (2018 年 1 月)

Google の Project Zero の Jann Horn は、CPU の弱点を 2017 年 6 月 1 日に Intel、AMD、ARM へ知らせ、2018 年 1 月に公表しました。記事は、CPU のデータのキャッシュにかかる時間を悪用すると、本来は読めないはずのメモリの中身を漏らせると書いています。同じ時期に、Meltdown の論文が 2018 年 1 月 3 日に arXiv に出ました。論文によると、Meltdown は普通のプログラムから OS の中のメモリ (パスワードなどを含む) を読めます。番号は、Meltdown が CVE-2017-5754、Spectre が CVE-2017-5753 と CVE-2017-5715 です。

崩れた前提は「キャッシュに当たったか外れたかは、外から見えない」です。当たれば速く、外れれば遅い。その時間の差を測れば、どの行がキャッシュに載ったかが分かり、そこから中身を割り出せました。速くするための仕組みの性質が、そのまま情報の漏れ口になった例です。

よくある問い

キャッシュメモリとメモリ (RAM) の違いは何ですか

キャッシュメモリは CPU のすぐ近くにある小さくて速い控えの置き場で、メモリ (RAM、主記憶) はプログラムが作業中のデータを広げる大きな置き場です。キャッシュには主に SRAM、主記憶には DRAM が使われ、SRAM の方が速い代わりに高価です。キャッシュはプログラムから直接は操作せず、CPU が自動で間に挟みます。

L1、L2、L3 キャッシュの違いは何ですか

数字は CPU からの近さの段で、L1 が一番近くて速く、一番小さい段です。L1 で外れると L2、L2 で外れると L3 を見て、それでも無ければ主記憶へ取りに行きます。Norvig の表では、L1 から読むのが 0.5 ナノ秒、L2 から読むのが 7 ナノ秒です。

ヒット率とは何ですか

ヒット率は、読んだ回数のうち、キャッシュに当たった回数の割合です。この頁の模型では、表を行の順に読むと 64 回中 56 回当たり、ヒット率は 0.875 です。当たりと外れの時間の差が大きいので、ヒット率が少し変わるだけで平均の待ち時間は大きく動きます。

確認クイズ

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。4 問すべてに正しく答えると、この回は修了で、石が緑に変わります (この端末のブラウザに残ります)。間違えた問いは、あとから解き直せます。

1 問目

キャッシュメモリを CPU と主記憶の間に置く一番の理由はどれですか。

2 問目

この頁の模型 (1 行に 8 個、置ける行は 4 本、LRU) で、番地 0 から 15 までを順に 1 回ずつ読みます。このコードを動かすと何が出ますか。

番地 0〜15 を順に読む

print(run(range(16)))

3 問目

時間の局所性の説明として正しいのはどれですか。

4 問目

置ける行が 4 本の LRU の模型で、5 本の行を順にくり返し読むと、当たりがほとんど無くなりました。理由はどれですか。

説明課題

次のことを、メモリの階層の図を思い浮かべながら、自分の言葉で 3〜4 行にまとめてください。書いたら、下の模範解答を開いて比べてください。

  • キャッシュに当たると速いのはなぜか。階層のどの段とどの段の差かを含めて
  • 8 行 8 列の表を、行の順に読む方が列の順に読むより当たりやすいのはなぜか

模範解答

自分の説明を書いてから開いて、比べてください。

この回の先の一歩

大きな表を行の順に回すか列の順に回すかのように、データを読む順番は処理の速さに直に効くので、遅い処理を直すときに最初に疑う所になります。

もっと深く

この回の続きを、ゆめさくのコースの章や道具で深められます。頁の中の確認クイズに答えたあと、手を動かす量を増やしたいときに使ってください。

一次資料

どれも 2026年10月27日に開いて、題と中身を確かめました。

次へ

道 3 コンピュータの中の回

  1. 3-1OS の役目とシステムコールOS が間に立つ理由頁で読む
  2. 3-2プロセスとスレッドスケジューラ、プロセス間通信頁で読む
  3. 3-3メモリの階層とキャッシュ局所性、当たりと外れ、L1 から主記憶までこの回
  4. 3-4取り合いと排他レースコンディション、Mutex頁で読む
  5. 3-5CPU が命令を実行する命令、レジスタ、クロック頁で読む

道 3 の頁へ戻るほかの道を見る