マルチコアプロセサのキャッシュコヒーレンシの維持

共通メモリ型のマルチプロセサの場合、コアごとに持っているプライベートキャシュの内容を矛盾が無い状態に保つキャッシュコヒーレンシの維持という面倒な問題がある。

共有メモリのシステムでは、あるプロセサコアがストア命令を実行して、あるアドレスのデータを書き変えると、他のプロセサコアから見ても、そのアドレスのデータは書き換わっていなければならない。しかし、どのプロセサコアのキャッシュもどのアドレスのデータでも保持できるということは、そのアドレスのデータは別のコアのキャッシュにも存在しうるということである。図3.6に示すように、プロセサコアP0が(1)であるメモリアドレスのデータをプライベートキャッシュに読み込み、その後、(2)でプロセサコアPiが同じアドレスのデータを自分のプライベートキャッシュに読み込む。この時点では、同じアドレスのデータが2つのコアのキャッシュに存在するが、そのデータは同じであり、問題はない。

図3.6 同一アドレスでデータが異なるという問題が発生してしまう

しかし、(3)でP0がプライベートキャッシュに書き込みを行うと、P0のキャッシュのデータが変わり、Piのキャッシュのデータとは異なってしまう。これでは、どのプロセサから見ても同一アドレスのデータ値は同じという共通メモリの条件を満たせない。また、P0のプライベートキャッシュの内容をメモリに書き戻し、その後にPiのプライベートキャッシュの内容をメモリに書き戻すと、P0の書き込んだデータは上書きされて消えてしまい、意図した動作をすることができないという問題もある。

このような問題が発生しないようにするには、何らかの方法で、プロセサコアのプライベートキャッシュ間のデータの不一致が発生しないようにする必要がある。このデータの不一致を無くすことを、キャッシュコヒーレンシを維持するという。

キャッシュコヒーレンシを維持するためには、キャッシュにデータを書き込む場合、同時に、そのアドレスのデータを保持している他のコアのプライベートキャッシュとメモリにも書き込みを行えばよい。しかし、この方法では、コアがストアを行うたびに他のコアのキャッシュへのデータの転送と、メモリへの書き込みが必要となり、プライベートキャッシュが無い場合と同じ回数のメモリ書き込みが必要となる。コモンバスの場合は、書き込むアドレスやデータは他のコアからも見えるので、他のコアのプライベートキャッシュへの書き込みは可能であるが、後述のクロスバスイッチの場合は、メモリ(あるいは、シェアードキャッシュ)への書き込みデータは他のコアのプライベートキャッシュには伝わらないので、コア-コア間を接続するクロスバを作る必要が出てくる。ということで、ストアをメモリや他のコアのプライベートキャッシュにも同時に書き込むという方法は負担が大きい。

もう1つの方法は、ストアの実行に際して、他のコアのプライベートキャッシュに同じアドレスのデータが存在するかどうかを問い合わせ、存在する場合は、そのデータを消去(インバリッド状態とするので、インバリデーションという)してもらうという方法である。

このように他のコアのプライベートキャッシュに同じアドレスのデータが存在するかどうかを知る動作をスヌープ(Snoop:嗅ぎまわる。漫画のSnoopyはこれから名づけられている)と呼ぶ。

そして、自分以外のすべてのコアから、プライベートキャッシュにそのアドレスのデータは存在しない、あるいは、存在したので消去したという応答を確認してからプライベートキャッシュに書き込みを行うという方法をとれば、他のコアにはそのアドレスのデータが存在しなくなるので不一致は生じない。

図3.7 他コアのキャシュの同一アドレスのデータを消去して矛盾を回避

図3.7に示すように、最初は、プロセサコアP0とPiのキャッシュに同一アドレスのデータが格納され、プロセサPjのキャッシュには、そのアドレスのデータはない状態であったとする。P0がストアを行おうとする場合、まず、(1)で書き込みアドレスと、そのアドレスのデータがキャッシュに有る場合は消去してくれというコマンドを、コマンド放送機構を通してP0以外のすべてのコアのキャッシュに送る。

この例では、Piは書き込みアドレスと同じアドレスのデータが自分のキャッシュ内にあるので、(2)でそのデータを無効化し、(3)で無効化したという応答を返す。一方、Pjも同じコマンドを受け取るが、Pjのキャッシュには書き込みアドレスのデータは無いので、(4)でそのアドレスのデータは無いという応答を返す。これらの応答は、応答集約機構でまとめられ、P0以外のすべてのプロセサからの応答が揃うと、(5)で他のコアのプライベートキャッシュには書き込みアドレスのデータは無くなり書き込みの準備が整ったことをP0に通知する。そして、(6)でコアP0は自分のキャッシュにデータを書き込む。

このようにすると、Piのキャシュのデータは無効化されているので、P0のキャッシュを書き変えてもP0とPiのキャッシュのデータが矛盾するという問題は発生しない。一方、P0のキャッシュのデータとメモリのデータは一致せず、P0のキャッシュには書き変えが行われた新しいデータ、メモリには書き変え前の古いデータが残った状態になる。

次に、図3.8に示すように、コアPjが、書き込みが行われたアドレスのデータを読もうとする場合を考える。この場合も、まず、(1)で、Pjがコマンド放送機構を通して、他のすべてのコアに書き換えを行った新データを持っていたら、それをメモリに書き戻してくれというコマンドを送る。コアP0のキャッシュには書き込みで変更された新データがあるので、P0は(2)で、そのデータをメモリに書き出す。そして、(3)でメモリへの書き出しの完了応答を通知する。一方、コアPiは、そのアドレスのデータを持っていないので、(4)でデータは持っていないという応答を返す。これらの応答は応答集約機構でまとめられて(5)で元のコマンドを出したPjに通知される。この時点では、新データはメモリに書き込まれているので、コアPjは(6)でメモリからデータを読み込む。

図3.8 書き変えデータの存在の問い合わせとメモリ書き出しで矛盾を回避

このように、データのReadに対しても他のすべてのコアに書き変えられた新データの有無を問い合わせ、新データを持つコアにはメモリへの書き出しを要求し、それが完了してからメモリからデータを読むようにすることにより、メモリに残っていた旧データを読んでしまうという問題を回避できる。

この説明から分かるように、キャッシュラインはデータとメモリアドレス情報以外に、そのデータが無効であることを示すInvalid、データが書き変えられており、メモリにあるデータより新しいデータであることを示すModified、メモリと同じデータが入っていることを示すSharedの3つの状態を記憶する必要がある。

図3.9 キャッシュはM,S,I状態を示すタグを持つ

Modified、Shared、Invalidの3状態を使ってキャッシュコヒーレンシを維持するやり方は、これらの状態の頭文字をとってMSIプロトコルと呼ばれ、共通メモリのマルチコア、マルチプロセサのプライベートキャッシュのコヒーレンシを維持する基本的な方法となっている。

これまで述べたように、この3状態を使えばキャッシュコヒーレンシを維持することはできるのであるが、通信回数やメインメモリのアクセス回数を減らして、性能を改善するやり方が、種々、開発されている。その代表的なものを見ていこう。