GPUのシェアードメモリとキャッシュ

DRAMで作られたメモリからデータを読み、処理を行ってメモリに書き戻すというのは時間が掛かり効率が悪い。このため、CPUでは小容量だが高速のメモリをキャッシュとして使うが、GPUでは伝統的にローカルな小容量のスクラッチパッドメモリを用いてきた。このタイプの小容量メモリは、CPUでは一般にローカルメモリと呼ばれるが、GPUの場合は、ワープ内の全スレッドで共用されることから「シェアードメモリ」と呼ばれている。

図3-49の(1)に示すように、プロセサ1がシェアードメモリに書き込みを行う場合、シェアードメモリのアドレスを指定して書き込みを行う。そして、書き込まれたデータは、そのアドレスのデータを上書きするが、他のプロセサのシェアードメモリやデバイスメモリには影響を与えない。

一方、(2)のキャッシュメモリへの書き込みは、デバイスメモリのアドレスを指定して行われ、そのアドレスは(3)のように、キャッシュのタグに記憶される。キャッシュは、デバイスメモリの完全な代理人であり、(4)のように、プロセサ2が同じデバイスメモリアドレスのデータを読もうとすると、(5)のようにハードウェアが、デバイスメモリからデータをプロセサ2のキャッシュにコピーして正しいデータが読めるように動作する。

つまり、コヒーレンシが維持されたキャッシュは、どのプロセサが読んでも、そのアドレスの最新のデータが得られるし、書き込めば、他のプロセサから見てもメインメモリにデータが書き込まれたように振る舞う。これに対して、シェアードメモリは自分だけが使うメモ用紙のような感じで、メインメモリの中から必要なデータを書き写して参照したり、自分の計算の結果を書き込んだりして使うことができる。しかし、何をシェアードメモリに入れるかはソフトウェアが決め、自分のシェアードメモリへの書き込みを行っても、それは他のシェアードメモリには影響しない。また、シェアードメモリの内容は自動的にデバイスメモリに反映されることはなく、どの内容をデバイスメモリに書き戻すかもソフトウェアの判断である。

図3-49 キャッシュメモリとシェアードメモリの違い

このため、シェアードメモリにはキャッシュのようなタグは必要ないし、他のプロセサのキャッシュとの整合性を保つコヒーレンシプロトコルを実装する必要もない。従って、ハードウェアとしての実装コストはキャッシュより安価で、タグマッチやコヒーレンシプロトコルを実行する必要がないので、より高速なメモリを作ることができる。

なお、GPUの場合は、異なる頂点やピクセルの処理の間には何の関係もなく、スレッド間の通信が要らない処理が大部分であることからキャッシュが必要ではなかったということもシェアードメモリが広く用いられた理由である。

シェアードメモリはキャッシュのようなコヒーレンス制御を必要とせず、キャッシュより高速に動作するので、シェアードメモリをうまく使うと性能の高いプログラムを作ることができるのであるが、その容量に制限があり、プログラマが意識して使わなければならない。CPUの場合は、キャッシュがソフトウェアには見えないところで、頻繁にアクセスするデータは高速にアクセスできるようにしてくれるのであるが、GPUでは、明示的にシェアードメモリを使うようにプログラムを書かなければならない。

また、ブロック内のスレッド間か、ブロックをまたぐスレッド間のデータのやり取りかでデータをどのメモリに格納するかも考えねばならない。慣れてくれば、大したことはないとも言えるが、やはり、キャッシュではなくローカルなシェアードメモリを使っていることは、GPUのプログラミングを取っき難いものにしていると言える。