ディレクトリ方式キャッシュコヒーレンシ ─ 多コアのスケーラビリティ
コア数が増えるとスヌープのブロードキャストが帯域を食い潰す。ディレクトリ方式は共有者を記録して必要な相手にだけ無効化を送り、数十〜数百コアでも一貫性を保てる設計の勘所を掴めます。
- 1.スヌープはコヒーレンス処理を全コアへブロードキャストするため、トラフィックがコア数に比例して増え、共有バス/リング前提の中小規模でしか成立しない。
- 2.ディレクトリ方式は各メモリブロックごとに「どのコアがコピーを持つか」を共有者ベクタやポインタで記録し、書き込み時はその相手にだけ無効化メッセージをユニキャストする。
- 3.要求と応答が複数往復するため Modified→遷移→Shared のような transient 状態が必須で、ディレクトリ自体のストレージはフルビットベクタだと O(コア数) で増えるためスケーラブルな符号化が課題になる。
なぜスヌープは大規模で破綻するのか
複数コアが同じメモリブロックを各自のキャッシュに持つと、片方の書き込みが他方に見えないコヒーレンシ違反が起きます。これを防ぐ古典的な手法がスヌープ方式です。各コアはコヒーレンス操作(読み出し要求・書き込み要求・無効化)を共有バスやリングへブロードキャストし、全コアが流れるアドレスを監視(スヌープ)して、自分が持つコピーの状態を更新・無効化します。MESI などの状態管理はキャッシュメモリの原理で扱った通りです。
スヌープの利点は単純さと低遅延(1往復で全員に届く)ですが、本質的な弱点はトラフィックがコア数に比例して増えることです。あるブロックを書き込むたびに、たとえ実際にコピーを持つコアが1つでも、全コアへ問い合わせが飛びます。コア数を N とすると、コヒーレンス帯域の需要はおよそ N に比例し、共有媒体の帯域は有限なので、ある規模で頭打ちになります。
スヌープは「全員が全アドレスを見る」前提に立つため、物理的に全員へ届く相互接続(共有バス、リング、限定的なメッシュ)が要ります。コアが数十を超えると、配線・スヌープフィルタ・帯域のいずれかが律速になります。実機ではスヌープフィルタで無駄な照合を間引きますが、ブロードキャストという根本構造は変えられません。
ディレクトリ方式:共有者を「知っている」場所を持つ
発想の転換は「全員に聞く」のをやめ、各メモリブロックについて誰がコピーを持つかを集中的に記録しておくことです。この記録がディレクトリで、典型的には各ブロックのホームノード(そのアドレスを所有するメモリ/ラスト・レベル・キャッシュ側)に置かれます。書き込み時はディレクトリを引いてコピーを持つコアだけに無効化をユニキャストするため、ブロードキャストが不要になります。
ディレクトリの1エントリは、最低限「状態」と「共有者の集合」を持ちます。
| 項目 | スヌープ方式 | ディレクトリ方式 |
|---|---|---|
| 通信パターン | ブロードキャスト | ホーム経由のユニキャスト/マルチキャスト |
| コヒーレンス帯域 | コア数に比例して増大 | 実際の共有者数に依存 |
| 必要な相互接続 | 順序付き共有媒体(バス/リング) | 任意のスケーラブルなネットワーク(メッシュ等) |
| 主な遅延 | 低(1往復) | 高め(ホーム経由で複数往復) |
| 主なコスト | 帯域 | ディレクトリのストレージ |
ディレクトリ方式は遅延と引き換えに帯域を節約する設計です。順序付きの共有媒体を必要としないため、メッシュやトーラスのようなスケーラブルなオンチップ網の上で数十〜数百コアまで素直に伸ばせます。CXL のコヒーレント相互接続のようなチップ外コヒーレンスでも、この「集中管理点を介す」考え方が土台になります。
共有者の符号化:ベクタとポインタ
ディレクトリが「誰が持つか」をどう表すかで、ストレージ量とスケーラビリティが決まります。
- フルビットベクタ: コアごとに1ビットを持ち、
{コア2, コア5}が共有なら該当ビットを立てます。任意の共有集合を正確に表せますが、1ブロックあたり N ビット必要で、ストレージが O(N) で増えます。 - 限定ポインタ: 共有者を「最大 K 個までのコア番号」で記録します。多くのデータは少数のコアでしか共有されないという経験則を突き、K がコア数 N 未満なら大幅に節約できます。共有者が K を超えたら、ブロードキャストへフォールバックするか1個を無効化して空ける(オーバーフロー処理)必要があります。
- 粗いベクタ(coarse vector): 1ビットを「コアのグループ」に対応させます。グループ内の誰か1つでも持てばビットを立てるため、無効化は当該グループ全体へ送られ、無駄な無効化が混じる代わりにビット数を圧縮できます。
| 符号化 | 1ブロックのコスト | 精度 | 弱点 |
|---|---|---|---|
| フルビットベクタ | N ビット | 正確 | コア数に比例して肥大 |
| 限定ポインタ(K個) | K×log2(N) ビット | K個までは正確 | オーバーフロー処理が必要 |
| 粗いベクタ | グループ数ビット | グループ単位で近似 | 無駄な無効化が発生 |
精度を取ればストレージが膨らみ、ストレージを削れば無駄な無効化が増える、という綱引きが符号化選択の本質です。
無効化メッセージと遷移(transient)状態
ディレクトリ方式の難しさは、1つの操作が複数のメッセージ往復に分解される点にあります。スヌープなら1回のブロードキャストで完結したものが、要求→ホーム照会→共有者への無効化→確認応答(ack)→要求元への許可、と段階を踏みます。この途中で別の要求が同じブロックに届くため、ブロックは「安定状態(M/E/S/I)」だけでなく**遷移状態(transient state)**を持つ必要があります。
典型的な書き込みアップグレード(S から M へ昇格)の流れを擬似的に示します。
1. コアA: ブロックX を S で保持中、書き込みたい → ホームへ Upgrade 要求
2. ホーム: ディレクトリ参照 → 共有者 {A, B, C} を確認
B, C へ Invalidate を送信、A を transient 状態にする
3. B, C : 自分のコピーを Invalid にし、ホーム(または A)へ Ack 返信
4. ホーム: 全 Ack を集めたら A へ書き込み許可、ディレクトリを「所有者=A, 状態=M」へ更新
5. コアA: M へ遷移し書き込み実行
ステップ2〜4の間、A のブロックは「S でも M でもない、Ack 待ちの中間状態」にあります。MOESI ベースのプロトコル仕様では、こうした SM_AD(S から M へ、データと Ack を待つ)のような遷移状態を明示し、その状態で別要求が来たときの振る舞いまで定義します。
遷移状態を設けず安定状態だけで設計すると、競合する要求が交差したときにデータ競合・デッドロック・二重所有が起きます。たとえば A の Upgrade 処理中に B が同じブロックの書き込み要求を出すと、無効化と新規共有が衝突します。遷移状態は「処理中につき、来た要求はキューイング/否定応答(NACK)する」という安全弁であり、コヒーレンスプロトコルの正しさの中核です。実機の検証はこの状態遷移の網羅性に最も労力を割きます。
ストレージのオーバーヘッドとスケーラビリティ
ディレクトリ方式の代償はディレクトリ自身が消費するメモリです。フルビットベクタでメモリ全体の各ブロックにエントリを持たせると、オーバーヘッドはおよそ次で見積もれます。
オーバーヘッド比 ≒ (状態ビット + N) / (ブロックサイズ × 8)
ブロック 64 バイト、N=256 コアなら 1ブロックあたり概ね 260 ビット強、データ 512 ビットに対して約 5 割という無視できない比率になります。コア数が増えるほどこの比率は悪化するため、緩和策が要ります。
- スパースディレクトリ: 主記憶の全ブロックではなく、現在キャッシュされているブロックだけにエントリを持つ。キャッシュ総容量はメモリよりはるかに小さいので、エントリ数を大幅に削れます。ただしセットアソシアティブな構造になり、エントリが溢れると、まだ使われているブロックを強制無効化(バックインバリデーション)するペナルティが生じます。
- 限定ポインタ/粗いベクタ: 前述の通り、共有者表現自体を圧縮して N 依存を弱める。
- 階層ディレクトリ: ディレクトリをツリー状に分割し、ローカルなコヒーレンスはノード内で閉じ、ノード間だけ上位ディレクトリで管理する。NUMA や複数チップレットの構成でスケーラビリティを稼ぎます。
多くの実装はラスト・レベル・キャッシュ(LLC)を**包含(inclusive)**にし、LLC のタグ配列にディレクトリ情報(共有者ビット)を相乗りさせます。LLC にあるブロックは必ず上位にもあるという包含性により、ディレクトリは LLC エントリ数ぶんで足り、別途巨大な配列を持たずに済みます。これがスパースディレクトリの一般的な実装形です。
「スヌープはブロードキャストゆえコア数に比例して帯域が増え、大規模で破綻」「ディレクトリは共有者を記録し無効化をユニキャスト」「フルビットベクタは O(コア数) のストレージ、限定ポインタ/粗いベクタで圧縮」「複数往復ゆえ遷移(transient)状態が必須」の4点が頻出です。スヌープ=帯域律速・低遅延、ディレクトリ=ストレージ律速・やや高遅延、という対比で覚えると応用問題に強くなります。
まとめ
- スヌープはコヒーレンス操作をブロードキャストするため、トラフィックがコア数に比例し、共有媒体前提の中小規模でしか成立しない。
- ディレクトリ方式はブロックごとに共有者を記録し、書き込み時にコピー保持コアだけへ無効化をユニキャストすることで、スケーラブルな網の上で多コアまで伸ばせる。
- 共有者の符号化はフルビットベクタ(正確だが O(コア数))・限定ポインタ・粗いベクタの間でストレージと無駄な無効化を天秤にかける。
- 操作が複数往復に分かれるため遷移(transient)状態が正しさの中核で、ディレクトリ自身のストレージはスパース化・包含キャッシュ相乗りで抑える。
コア間をつなぐ物理層の事情はPCI Express の階層構造が、命令側でメモリ待ちを隠す仕組みはアウトオブオーダー実行が掘り下げます。可視順序そのものの規約はメモリオーダリングとバリアを併読してください。
CPU/メモリ/ディスク Article
ディレクトリ方式キャッシュコヒーレンシ ─ 多コアのスケーラビリティを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
キャッシュコヒーレンシ
比較で見る軸
難易度: advanced / カテゴリ: CPU/メモリ/ディスク / タグ数: 5
導入後に効く点
ディレクトリ方式は各メモリブロックごとに「どのコアがコピーを持つか」を共有者ベクタやポインタで記録し、書き込み時はその相手にだけ無効化メッセージをユニキャストする。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- CPU/メモリ/ディスク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「キャッシュコヒーレンシ / ディレクトリ」に近いか確認する。
- 強みである「スヌープはコヒーレンス処理を全コアへブロードキャストするため、トラフィックがコア数に比例して増え、共有バス/リング前提の中小規模でしか成立しない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。