キャッシュは、あるリソースにアクセスする際のパフォーマンスを向上させるために使用することができます。図のように、同じリソースに対してこのようなキャッシュが複数あると、問題が発生することがあります。キャッシュコヒーレンスまたはキャッシュコヒーレンシーとは、リソースのすべてのキャッシュが同じデータを持ち、キャッシュ内のデータが意味を持つようにするための仕組み(すなわちデータの整合性)を指します。キャッシュコヒーレンスは、より広い概念であるメモリコヒーレンス(メモリ整合性)の一部分です。

共通のメモリ資源のキャッシュが多数存在する場合、キャッシュ内のデータが意味をなさなくなったり、あるキャッシュが他のキャッシュと同じデータを持たなくなったりする問題が発生することがあります。この問題が発生する一般的なケースは、マルチプロセッシングシステムのCPUのキャッシュです。図にあるように、あるコア(トップクライアント)が以前に読み込んだメモリブロックのコピーを持っていて、別のコア(ボトムクライアント)がそのメモリブロックを変更した場合、トップクライアントは知らないうちに古い(無効な)値を参照してしまう可能性があります。キャッシュコヒーレンスの仕組みは、このような競合を管理し、キャッシュとメモリの間の一貫性を維持するためにあります。

なぜ問題になるのか(直感的な例)

  • コアAが変数Xを読み取りキャッシュに置く。
  • コアBが同じ変数Xを書き換える。
  • コアAは自分のキャッシュに古い値が残っているため、新しい値を見ないまま処理を続ける可能性がある。

このような状況は、プログラムの正しさ(特に並列処理での同期)を損ね、バグや予期しない振る舞いを生じさせます。したがって、ハードウェアとソフトウェアの両方で対策が必要です。

代表的なキャッシュコヒーレンスプロトコル

キャッシュコヒーレンスを保つために、ハードウェアは様々なプロトコルを使います。代表的なもの:

  • MSI(Modified, Shared, Invalid)— 基本的な3状態プロトコル。書き込み可能な状態(Modified)、共有状態(Shared)、無効状態(Invalid)を持つ。
  • MESI(Modified, Exclusive, Shared, Invalid)— MSIにExclusive状態を加え、読み込みの最適化を行う。多くの商用プロセッサで採用。
  • MOESIなど派生形— Ownership状態などを追加して、コア間でのデータ転送を最適化する。

これらのプロトコルは、各キャッシュラインの状態を管理し、読み書きの際にどのようなアクション(例:他キャッシュへの無効化通知、更新のブロードキャスト)を行うかを決めます。

書き込みの処理方式:Write-Invalidate と Write-Update

  • Write-Invalidate:あるコアがデータを書き換える際、他のコアの該当キャッシュラインを「無効化」する。最も一般的でネットワークトラフィックを抑えやすい。
  • Write-Update(Write-Broadcast):書き換えた新しい値を他コアに送って更新させる。頻繁な書き込みが共有されるケースでは有利だが、更新のオーバーヘッドが大きくなる。

プロトコルの実装方式:Snooping と Directory ベース

  • Snooping(スヌーピング)方式:バス上のトランザクションを各キャッシュが監視(snoop)して状態を更新する。バスが共有される小〜中規模のシステムで有効。
  • Directory(ディレクトリ)方式:各メモリブロックに対してどのキャッシュがコピーを持っているかを管理するディレクトリを用いる。大規模・分散共有メモリ(NUMAなど)でスケーラブル。

よくある問題:フォールスシェアリング(False Sharing)

フォールスシェアリングは、異なるスレッドが独立した変数AとBを更新しているにもかかわらず、それらが同じキャッシュラインに格納されているために無駄な無効化や転送が発生する現象です。これにより性能が大幅に低下することがあります。対策としては、データの配置(パディングやアラインメント)やスレッド設計の見直しが有効です。

キャッシュコヒーレンスとメモリ一貫性(Consistency)の違い

コヒーレンスは単一のメモリ位置(アドレス)に着目し、その位置を読み書きする全てのプロセッサが同じ順序でその位置の更新を観測できることを保証します。一方、メモリ一貫性(メモリコンシステンシーモデル)は複数のメモリ位置にまたがる操作の順序保証(例えばリード/ライトの順序)について定義します。実用上、両方が正しく動作することで並列プログラムの正当性が保たれます。

ハードウェア・ソフトウェアでの対策とプログラミング上の注意点

  • ハードウェア:上記のコヒーレンスプロトコル、インターコネクト設計(リング、メッシュ、専用コヒーレンスコントローラ)で整合性を保つ。
  • OS/ランタイム:ページの配置やスケジューリングで局所性を高め、コヒーレンス負荷を軽減する。
  • プログラマ/ライブラリ:
    • 原子操作(atomic)やメモリフェンス(memory barrier)を正しく使う。
    • 共有データの粒度を見直し、ロックの粒度やスレッド間のデータ分離を行う。
    • フォールスシェアリングを避けるために構造体のパディングやアラインメントを調整する。
    • 高レベル並列ライブラリ(スレッドプール、並列アルゴリズム)を利用して誤りを減らす。

設計上のトレードオフ

  • 一貫性を厳密に保つほど通信オーバーヘッドやレイテンシが増える。
  • 小〜中規模ではスヌーピング+Write-Invalidateが有効だが、大規模クラスタや分散共有メモリではディレクトリ方式やソフトウェアベースの調整が必要。
  • 性能最適化とプログラムの単純さ(正しさ)はしばしばトレードオフになるため、設計時に優先度を明確にする必要がある。

まとめ

キャッシュコヒーレンスは、マルチコアやマルチプロセッサ環境でデータの正しさを保つための重要な仕組みです。ハードウェアレベルのプロトコル(MSI/MESIなど)やスヌーピング/ディレクトリ方式、さらにソフトウェア側の設計(同期、データ配置、原子操作)が組み合わさって機能します。並列アプリケーションの性能最適化では、コヒーレンスオーバーヘッドやフォールスシェアリングを意識したデータ設計と同期手法の選択が重要です。