キャッシュラインのフォールスシェアリング

Write Invalidate方式のキャッシュの1つのキャッシュラインにプロセサAが書き込みを行うと、他のプロセサの同一アドレスのキャッシュラインのコピーは無効化される。そして、他のプロセサBがそのアドレスを読むと、MESIプロトコルの場合は、プロセサAはキャッシュラインをメモリに書き戻し、プロセサBはメモリからそのデータを読み込む。

その後に、プロセサBがそのアドレスに書き込みを行う場合は、プロセサAを含む他のプロセサのキャッシュラインを無効化し、プロセサBは自分のキャッシュに書き込みを行う。このように、同じアドレスに交互に異なるプロセサが書き込みを行うと、メモリ経由でキャシュラインが頻繁に移動することになり、非常に処理時間がかかってしまう。MOSIやMOESIプロトコルの場合は、プロセサのキャッシュ間でデータ転送が行われるのでメモリ経由よりは効率的であるが、それでも64バイト~256バイトのサイズのキャッシュラインを転送するには時間がかかる。

まったく同じアドレスに、複数のプロセサが交互に書き込みを行う必要がある場合は、このような転送が発生するのは止むを得ない面があるが、キャッシュラインは、IntelのCoreプロセサでは64バイトであり、4バイトのデータであれば16個のデータを含むことになる。したがって、プロセサAが最初の4バイト、プロセサBが次の4バイトに書き込みを行う場合でも、同じようにキャッシュラインが行き来してしまうことになる。このように本来は別のアドレスであるが、キャッシュライン単位では同じになってしまう偽の共有状態であり、これをFalse Sharingと呼ぶ。

A、B、C、Dの各プロセサが書き込む変数をグローバル変数として、

int ProcA、 ProcB、 ProcC、 ProcD

のように連続して定義すると、連続したアドレスに配置されてしまい、False Sharingが起こって性能が低下するが、これを別々のキャッシュラインになるように配置すれば、キャッシュラインの行き来が発生せず、性能の低下を防ぐことが出来る。例えば、キャッシュラインのサイズが64バイトでintが4バイトの場合、

int ProcA、 DummyA[15]、ProcB、 DummyB[15]、 ProcC、 DummyC[15]、 ProcD

のように定義すると、ProcAとProcBのアドレスは64バイト離れることになり別キャッシュラインとなって、False Sharingが発生しなくなる。そしてProcC、ProcDについても同様である。このような変数定義は、多少、メモリの無駄遣いのようであるが、フォールスシェアリングでプログラムの実行性能が低下している場合には画期的な性能改善が得られるので、覚えておいて損はない。