キャッシュメモリ
キャッシュメモリ (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つ選択される)に対応したフレームアドレスとを比較することでキャッシュのヒットを検出する。このフレームアドレス格納バッファが(図中)タグである。複数セットのタグを持てば同じエントリアドレスでも複数データの格納を行うことが可能となる。このタグのセット数(ウエイ)を連想度と呼ぶ。データ格納構造の相違は連想度の相違でもある。
ダイレクトマップ方式 (Direct Mapped)
1組のタグにより構成(連想度1)されるデータ格納構造。アドレスにより一意に配置が決まるため、タグの構造が非常に単純。だが、同一エントリに異なるフレームアドレスが転送されると必ずラインの入れ替えが発生する。ラインの入れ替えが頻発しスループットが落ちることをキャッシュスラッシングというが、この状態が起こりやすくヒット率は他の方式に比べ高くない。
セットアソシアティブ方式 (Set Associative)
複数のタグにより構成(連想度2以上)されるデータ格納構造。同一エントリに異なるフレームアドレスのデータを複数格納することができる。連想度が上がるほどキャッシュヒット率は上昇するが製造は困難になっていくため、システムによりバランスのよい実装が異なる。n個のタグにより構成された場合、nウエイセットアソシアティブ方式と呼ぶ。最近はCAM (連想メモリ:Content Addressable Memory)がタグとして使われ出し、32など非常に高い連想度を実装できるようになってきた。ダイレクトマップ方式や下記のフルアソシアティブ方式はこの方式の特殊な場合である。
フルアソシアティブ方式 (Fully Associative)
エントリアドレスによる振り分けはなく、全てのラインが検索対象となる構造。従って連想度はライン数分となる。キャッシュスラッシングは起こり難くヒット率は最も優れているが、実装コストや複雑度の面から通常用いられることはない。
ラインの入替え(リフィル)は該当エントリの全ラインにデータが格納されてなお同一エントリ新規フレームアドレスが入力されてキャッシュミスした(ヒットしなかった)場合に発生する。その場合どのラインを掃出して新規アドレスと入替えるかのアルゴリズムによってキャッシュのヒット率が変動する。代表的なアルゴリズムを記す。
ラウンドロビン (Round Robin)
リフィル対象となるラインを順番に交代させる方法。各ラインのアクセス頻度に拘らず順番にリフィルを行うため、あまりヒット率が高くない。
LRU (Least Recently Used)
最も古くアクセスされたラインをリフィルする方法。時間的局所性に鑑みれば、過去最もアクセスのなかったラインは将来にわたってもアクセスされる可能性は少ないと言える。従ってこの方法はヒット率がかなり高い方法としてよく採用されている。ただし各ラインごとにアクセス順履歴を持ちアクセスがある度に頻繁に履歴を入替えるため、複雑な構成となりアクセス性能に影響が出る場合がある。
ランダム (Random)
リフィルラインの選択をランダムに行う方式。各ライン毎にリフィル用機構を持つ必要がなくなるため構成が簡易になる。ヒット率はラウンドロビンよりは良いとされる。
CPUキャッシュは命令キャッシュとデータキャッシュの2種類が搭載されている場合が多い。命令キャッシュはプログラムという静的なデータを扱うのでデータ更新は存在しないが、データキャッシュはメモリへのライト動作があるためデータ更新が存在する。更新されたデータはいずれかのタイミングで下位レベルのメモリにも反映される必要があり、そのタイミングの相違により2つのアルゴリズムが存在する。
ライトスルー方式 (Write Through Algorithm)
CPUがメモリ書き込みを行ったら、キャッシュにストアすると同時に下位レベルのメモリにも書き戻す方式。必ず下位レベルのバスが活性化するため、バスの競合や下位レベルの低いスループットに律速されるなどの制約はあるが、単純な構成で実現でき、またデータのコヒーレンシを保つことが容易である。出力段にライトバッファを設けることにより、単一CPUであればライトバック方式に比べ遜色のない性能が期待できる。そのためCPUのL1キャッシュなどに実装される場合が多い。
ライトバック方式 (Write Back Algorithm)
CPUがメモリ書き込みを行っても、条件が整わない限りキャッシュに留まりメモリへの書き戻しを行わない方式。書き戻す条件は対象エントリにウエイ数以上のフレームアドレスのリード/ライトが行われる、他のバスマスタが対象エントリが保持しているアドレスに対しアクセスを行った時にコヒーレンシを保つために行うなどがある。ライトスルー方式に対し下位レベルのバスが競合を起こしにくく、マルチCPU構成に向くため、記憶階層の同一レベルに複数のキャッシュが接続されているようなL2キャッシュに実装される。ライトミス時に2つのアプローチがある。一つは、Write allocate であり、もうひとつが No-write allocate である。
Write allocate は fetch on write とも呼ばれる。ライトミスしたアドレスを含むラインがキャッシュにロードされた後、ライトが実行される。このアプローチでは、ライトミスとリードミスは同様の動作となる。
No-write allocate は write-no-allocate または write around と呼ばれる。ライトミスしたアドレスのデータはキャッシュにロードされず、データは 下位の記憶階層に書き込まれる。このアプローチでは、データロードは、リードミス時にのみ発生する。
マルチCPU/キャッシュ構成など複数のバスマスタが存在し、各々がデータ更新を行った場合でも最新の正しいデータにアクセスできるよう保つべきデータの一貫性のことをキャッシュコヒーレンシもしくはキャッシュコンシステンシ (Cache Consistency) という。データ更新に上記ライトバック方式を用いた場合など、キャッシュに更新されたデータが滞留して主記憶装置など下位レベルのメモリには最新のデータが存在しない可能性がある。この時に複数のCPUが同一の記憶領域を参照/更新しようとすると、データの不整合が起こり正しい結果が得られないため、これを解決しどのCPUも必ず最新のデータにアクセスできるようにする必要がある。このための代表的なアルゴリズムにスヌープ方式やディレクトリ方式、共有キャッシュがある。
スヌープ方式 (Cache Snooping)
キャッシュコヒーレンシのアルゴリズムにおいて、特に各キャッシュ自身に搭載される方法としてスヌープ方式(スヌープキャッシュ)がある。これは各々のキャッシュが自身や他CPUのキャッシュのライン更新状態を把握/管理し、他のキャッシュと更新状態の情報を交換することで、どのキャッシュに最新のデータが存在するかを知り、各キャッシュが必要なときに最新のデータを取得できるように自身の状態を変更したりラインのパージを行う。この情報交換は共通のデータバスを介して行われるため、情報の通知と実際のデータ転送との順序が保たれ、破綻を起こすことはない。逆に共通バスを持たない分散型メモリシステムには用いることが困難などの制約もある。このプロトコルとして下記のものが知られている。
無効型プロトコル (Invalidate Protocol)
複数のキャッシュから参照があるアドレスに対しあるキャッシュが更新を行う場合、そのアドレスはダーティであるとして参照中の全キャッシュの該当ラインを無効化する。これにより更新されたラインがありながら他のキャッシュで古いデータをキャッシングしている状態がなくなり、コヒーレンシが保たれる。MESI(Illinoisプロトコル)、MOSI(Berkeleyプロトコル)などがある。
更新型プロトコル (Update Protocol)
複数のキャッシュが参照しているアドレスに対してデータ更新を行うときはライトスルー型となり、単独でアクセスしている場合はライトバック型となるような制御を行うことで更新データを行き渡らせコヒーレンシを保つ。MEI(Fireflyプロトコル)、MOES(DRAGONプロトコル)などがある。
ディレクトリ方式 (Directory-based Protocol)
スヌープ方式と異なり、メモリの一貫性をディレクトリと呼ぶ専用領域にて一元管理する方式。この領域は実装上の各メモリ領域に分散してよく、分散メモリ型システムに適している。
共有キャッシュ (Shared Cache)
1つのキャッシュに対し複数のCPUが参照できるような構成を持つキャッシュ。1チップに集積された複数のCPUを扱うなど限定的な場面ではキャッシュコヒーレンシを根本的に解決するが、キャッシュ自体の構造が非常に複雑となる、もしくは性能低下要因となり、多くのCPUを接続することはより困難となる。
プリフェッチ (Pre-fetch)
CPUが専用命令などによりあらかじめデータをキャッシュに汲んでおく動作。データの流れがある程度予測できるような特定のソフトウエアアルゴリズムは、先んじてプリフェッチを行うことで実際にデータが必要な場面で余分なレイテンシがかかることなくスムーズに処理を行うことができる。例えばストリーミング処理のようなデータの流れや処理量などが単純で予測しやすい処理などは、プリフェッチを行うことで大幅に性能向上する場合がある。