キャッシュメモリ

キャッシュメモリ (cache memory) は、CPUなど処理装置がデータや命令などの情報を取得/更新する際に主記憶装置やバスなどの遅延／低帯域を隠蔽し、処理装置と記憶装置の性能差を埋めるために用いる高速小容量メモリのことである。略してキャッシュとも呼ぶ。コンピュータは以前から記憶装置や伝送路の性能が処理装置の性能に追いつけず、この差が全体性能に対するボトルネックとされてきた（ノイマンズ・ボトルネック）。そしてムーアの法則に基づく処理装置の加速度的な高性能化により現在ではますますこの差が拡大されている。キャッシュメモリは、記憶階層の観点からこれを解消しようとするものである。 主に、主記憶装置とCPUなど処理装置との間に構成される。この場合、処理装置がアクセスしたいデータやそのアドレス、状態、設定など属性情報をコピーし保持することで、本来アクセスすべき記憶装置に代わってデータを入出力する。通常はキャッシュメモリが自動的にデータ保存や主記憶装置の代替を行うため、基本的にCPUのプログラムなど処理装置側がキャッシュメモリを意識する必要はない。 キャッシュの一般的な概念はキャッシュ (コンピュータシステム)を参照のこと。 キャッシュメモリは再利用データのキャッシングによる実効データ帯域の増加という意義をもつ。 例えば SGEMV（単精度浮動小数点の行列ベクトル積）を考える。2.0 GHzで動作する Haswell CPUのシングルコアはピーク時に128GB/sのデータアクセスを要求する (8 [FMA/inst.] ÷ 0.5 [CPI=cycle/inst.] * 2.0G [Hz=cycle/sec] * 4 [Byte/FP32])。一方プロセッサ-メインメモリ間のレイテンシは数百サイクルであり、並列ロードをおこなっても高々5GB/sしかデータを読み出せない。すなわちメモリ律速でCPU性能の5%以下しか引き出すことができない。もし行列をキャッシュに載せきることが出来れば、よりレイテンシの小さいキャッシュメモリからデータを供給し高いデータ帯域を確保できる。 キャッシュメモリは、通常は下位レベルの記憶装置より小容量で高速なスタティックRAMを用いて構成される。データ本体の一部とそのアドレス、フラグなど属性情報のセットを固定容量のメモリに格納する構造で、データ格納構造、ライン入替え、データ更新方式、キャッシュ階層などに多数のアーキテクチャが存在する。以前はCPUチップの外部に接続されていたが、LSIの集積度の向上や要求速度の上昇に伴いCPUチップ内部に取り込まれることが普通となった。 記憶階層をもつキャッシュメモリをマルチレベルキャッシュ（英: multi level caches）という。CPUとメモリの性能差の拡大、マルチスレッドなどアクセス範囲の拡大に対応するために導入される。CPUに近い側からL1キャッシュ（レベル1）、L2キャッシュ（レベル2）と呼ばれ、2013年時点ではL4キャッシュまでCPUに内蔵する例も存在する。CPUから見て一番遠いキャッシュメモリの事をLLC（Last Level Cache）と呼ぶ事もある。 キャッシュメモリはデータをライン(ブロック)と呼ぶある程度まとまった単位で管理する(例えばIntel Pentium 4の8kByte L1キャッシュはラインサイズ64Byte)が、データのアクセス要求があった時にそのデータがキャッシュに存在しているか、あるならどのラインかなどを瞬時(多くの場合1サイクルのスループット)に検索する必要がある。そのためデータ格納アドレスの一部、具体的にはライン単位アドレスの下位数ビット(エントリアドレス)によりある程度の格納位置を限定することで検索速度を高める。各ラインにはライン単位アドレスの上位ビット、即ちフレームアドレスを格納しておき、キャッシュ検索時には検索アドレスのフレームアドレス部と、キャッシュ内に格納されている検索エントリアドレス位置（エントリアドレス部をデコードしラインが1つ選択される）に対応したフレームアドレスとを比較することでキャッシュのヒットを検出する。このフレームアドレス格納バッファが（図中）タグである。複数セットのタグを持てば同じエントリアドレスでも複数データの格納を行うことが可能となる。このタグのセット数(ウエイ)を連想度と呼ぶ。データ格納構造の相違は連想度の相違でもある。 ラインの入替え(リフィル)は該当エントリの全ラインにデータが格納されてなお同一エントリ新規フレームアドレスが入力されてキャッシュミスした（ヒットしなかった）場合に発生する。その場合どのラインを掃出して新規アドレスと入替えるかのアルゴリズムによってキャッシュのヒット率が変動する。代表的なアルゴリズムを記す。 CPUキャッシュは命令キャッシュとデータキャッシュの2種類が搭載されている場合が多い。命令キャッシュはプログラムという静的なデータを扱うのでデータ更新は存在しないが、データキャッシュはメモリへのライト動作があるためデータ更新が存在する。更新されたデータはいずれかのタイミングで下位レベルのメモリにも反映される必要があり、そのタイミングの相違により2つのアルゴリズムが存在する。 マルチCPU/キャッシュ構成など複数のバスマスタが存在し、各々がデータ更新を行った場合でも最新の正しいデータにアクセスできるよう保つべきデータの一貫性のことをキャッシュコヒーレンシもしくはキャッシュコンシステンシ (Cache Consistency) という。データ更新に上記ライトバック方式を用いた場合など、キャッシュに更新されたデータが滞留して主記憶装置など下位レベルのメモリには最新のデータが存在しない可能性がある。この時に複数のCPUが同一の記憶領域を参照／更新しようとすると、データの不整合が起こり正しい結果が得られないため、これを解決しどのCPUも必ず最新のデータにアクセスできるようにする必要がある。このための代表的なアルゴリズムにスヌープ方式やディレクトリ方式、共有キャッシュがある。 コヒーレンシの明示的な制御が必要となるような場合を除き、キャッシュメモリの存在はソフトウェアの挙動に対しては透過的である。一方、性能面ではキャッシュメモリの存在や仕様を意識することにより向上が図れることが知られている。 Solaris 2.4カーネルにて採用されたスラブアロケーション（英語版）では、構造体の特定のメンバにアクセスが集中する傾向を利用し、各スラブにてオブジェクト領域の先頭に異なるスラブ先頭からのオフセットを与えることにより、キャッシュライン内で頻繁にアクセスされる位置を分散させている。当時サンが販売していた製品ではメモリインターリーブに併せてキャッシュライン内をさらに複数のメモリバスに分割して割り当てていた。このためキャッシュライン内でアクセスが頻繁な箇所が特定の位置に集中するとキャッシュラインだけでなくメモリバスの負荷も分散されなくなってしまうことが問題となっており、スラブアロケーションはその解決策として使用された。 同一のキャッシュライン内に頻繁に更新されるデータとほとんど更新されないデータが共存していると、システム全体ではメインメモリへの書き戻しが必要なキャッシュライン数が増えてしまう。両者がキャッシュライン上で分離されるようにデータを配置すると、書き戻しが必要なキャッシュラインの数を減らして効率を上げることができる。LinuxカーネルやFreeBSDなど、GNU ldないしはその互換リンカをビルドに用いているOSでは、ほとんど更新されないデータをELFのある特定のセクションに定義することにより、そのようなデータだけを集めた上でキャッシュライン境界に整列させている。なお、上記のセクションに対してそのようなアドレス配置を実際にさせているのは、カーネルのリンク時に使用するリンカスクリプトである。