キャッシュとは、コンピュータサイエンスで使われる用語です。キャッシュ発音は「キャッシュ」/ˈkæʃ/ KASH)の背後にある考え方は非常に単純です。非常に多くの場合、計算の結果を得ることは非常に時間がかかるので、結果を保存することは一般的に良いアイデアです。2種類の記憶媒体が使われています。1つは通常かなり大きなメディアですが、アクセスは「遅い」もので、もう1つはかなり高速にアクセスできますが、一般的には小さいものです。キャッシングの非常に基本的な考え方は、データのコピーを持つためにアクセスが速いメディアを使うことです。コピーとオリジナルの間に違いはありません。オリジナルのデータにアクセスするには、長い時間がかかるかもしれませんし、コストがかかるかもしれません(例:解くのに時間がかかる難しい問題の結果など)。このような理由から、単純にキャッシュからコピーしたデータを利用する方がはるかに「安い」のです。別の言い方をすれば、キャッシュとは、よく使うデータのコピーを一時的に保管しておく場所のことです。このキャッシュにデータのコピーがあると、元のデータを再取得したり再計算したりするよりも、このコピーを使った方が早いのです。これにより、データにアクセスするのに必要な平均時間が短くなります。新しい値をキャッシュに入れるということは、古い値を置き換える必要があることを意味することが多い。どのように置き換える値を選択するかについては、異なる考え(通常は「戦略」と呼ばれます)があります。

バッファはキャッシュと非常に似ています。バッファ内のデータにアクセスするクライアントはバッファがあることを知っていますが、バッファはアプリケーションによって管理されています。キャッシュの場合、データにアクセスするクライアントはキャッシュがあることを意識する必要はありません。

典型的なコンピュータアプリケーションは、非常に似た方法でデータにアクセスします。データが「ブロック」に構造化されていて、個別にアクセスできるとします。アプリケーションがブロックにアクセスするとき、元のブロックに「近い」ブロックにアクセス(または参照)する可能性が非常に高くなります。これは参照の局所性として知られています。このような「ロカリティ」にはさまざまな種類があります。参照のロカリティは、コンピューティングの多くの分野でキャッシュがうまく機能する理由の一つです。

うまく機能するためには、データ全体の量に比べてキャッシュは小さくしなければなりません。キャッシュが大きければ大きいほど、エントリを検索するのに時間がかかります。また、大きなキャッシュは構築コストが高くなります。

キャッシュの目的と利点

  • 高速化 — 頻繁に使われるデータを速い記憶領域に置くことで、平均アクセス時間を下げる。
  • 帯域幅節約 — 遠隔のストレージや高コストな計算を繰り返さずに済むため、ネットワークやCPUの負荷を減らせる。
  • コスト低減 — 高速メモリは高価なので、小さなキャッシュを用いて大容量の安価な記憶を補助する。

キャッシュの基本的な仕組み

  • ヒットとミス — 必要なデータがキャッシュにあれば「キャッシュヒット」、なければ「キャッシュミス」。ミスが発生すると元の(遅い)媒体からデータを取得し、キャッシュに格納する。
  • 平均アクセス時間(AAT) — AAT = ヒット率 × ヒット時間 + ミス率 × ミスペナルティ。ミスペナルティは遅い媒体から読み込む時間など。
  • キャッシュライン(ブロック) — 多くのキャッシュはデータを固定長のブロック単位(キャッシュライン)で扱う。空間的局所性を活かすために、要求されたデータ周辺も一緒に読み込む。

参照の局所性(ロカリティ)の種類

  • 時間的局所性(Temporal locality) — 一度参照したデータは近い将来に再度参照されやすい。
  • 空間的局所性(Spatial locality) — あるアドレスを参照すると、その近傍のアドレスも参照されやすい。

キャッシュの種類(用途別)

  • CPUキャッシュ — L1(最速・最小)、L2、L3(共有で大きめ)などの階層を持つ。命令キャッシュとデータキャッシュに分かれることもある。
  • OS/ページキャッシュ(ディスクキャッシュ) — OSがディスクI/Oのためにメモリの一部を使う。ディスクアクセスを減らす。
  • ブラウザキャッシュ — Webページや画像、スクリプトを保存して再読み込みを速くする。
  • CDN/プロキシキャッシュ — ネットワーク越しのコンテンツ配信を高速化するための中間保存。
  • アプリケーションキャッシュ(メモリキャッシュ) — RedisやMemcachedのようなインメモリキャッシュで、頻繁に参照されるデータを保存する。

配置方法・連想度(アソシアティビティ)

  • 直接マップ(Direct-mapped) — 各メモリアドレスがキャッシュ内の一つの場所にのみマップされる。実装が単純だが競合が起きやすい。
  • 集合連想(Set-associative) — キャッシュを複数のセットに分け、各セット内で複数エントリを持てる方式。N-way と呼ぶ。
  • 完全連想(Fully-associative) — 任意のエントリが任意の場所に入れられる(柔軟だがコストが高い)。

置換戦略(エビクションポリシー)

キャッシュが満杯のとき、どのエントリを追い出すかを決めるルールが必要です。代表的な戦略は次の通りです:

  • LRU(Least Recently Used) — 最も長く使われていないものを破棄する。一般的で効果的だが実装コストがかかる場合がある。
  • FIFO(First-In, First-Out) — 最も古く入ったものから追い出す。
  • LFU(Least Frequently Used) — 使用頻度が最も低いものを破棄する。
  • ランダム — ランダムに選ぶ。単純でハードウェア実装が容易。
  • CLOCK — 擬似LRU。ハードウェア向けに効率化されたアルゴリズム。
  • ARCや2Q — 頻度と最近性の両方を考慮する先進的アルゴリズム。

書き込みポリシー(Write policies)

  • Write-through — キャッシュに書き込むと同時にバックエンド(メインメモリなど)にも書く。データ整合性は保ちやすいが書き込み遅延が大きい。
  • Write-back(Write-back / write-behind) — キャッシュに書き込むだけで、追い出すときにまとめてバックエンドに書く。書き込み回数を減らせるが、データ整合性管理や障害時の復旧が必要になる。
  • Write allocate / no-write allocate — 書き込み時にキャッシュにブロックをロードするかどうかの方針。

整合性・無効化(coherency / invalidation)

分散キャッシュやマルチコア環境では、複数のキャッシュ間で同じデータの整合性を保つ必要があります。キャッシュコヒーレンシプロトコル(MESIなど)や、分散システムではTTL(Time To Live)、明示的な無効化(invalidate)/更新通知(invalidate/refresh)などが用いられます。

キャッシュの性能指標とトレードオフ

  • ヒット率(Hit rate) / ミス率(Miss rate) — キャッシュの基本的な指標。
  • ヒット時間(Hit time) — キャッシュから読み出すのにかかる時間。
  • ミスペナルティ(Miss penalty) — ミス時に遅い媒体から取り寄せる時間。
  • 容量・レイテンシ・コストのトレードオフ — 大きなキャッシュはヒット率を上げるが遅延・消費電力・コスト増になる場合がある。

キャッシュに関する実務上の注意点・ベストプラクティス

  • キャッシュキー設計は重要。キーに含める情報が不適切だとヒット率が下がる。
  • 無効化(cache invalidation)は難しい問題。書き換えが頻繁なデータは短いTTLや同期更新を検討する。
  • キャッシュスラム/スタンピード(複数の要求が同時にキャッシュミスしてバックエンドを圧迫する)を防ぐため、ロック、リクエスト合流(request coalescing)、遅延再試行を使う。
  • キャッシュウォーミング(起動時に主要データを事前に読み込む)でスロースタートを避ける。
  • 監視とメトリクス(ヒット率、レイテンシ、ミス時のバックエンド負荷)を継続的に確認する。

短いまとめ

キャッシュは「高速だが小さい」記憶と「遅いが大きい」記憶を組み合わせて、頻繁に使うデータのアクセスを速くする仕組みです。参照のロカリティを利用し、適切なサイズ、配置方式、置換戦略、書き込みポリシーを選ぶことでシステム全体の性能を大幅に向上させられます。一方で、整合性管理や無効化、キャッシュ設計の誤りは性能低下やバグの原因になるため、実装と運用には注意が必要です。