GPUのスレッドあたりのキャッシュ容量

最近のハイエンドGPUは、NVIDIAで言うとSM(Streaming Multiprocessor)が1チップに複数(GK110チップの場合15個)集積されているという構造になっている。そして、KeplerアーキテクチャのGPUの場合は、1つのSMに192個のCUDAコアと呼ぶ実行ユニットが入っている。つまり、192×15=2880個の実行ユニットが1チップに搭載されている。

GK110チップのチップ写真。右下の白い枠が1個のSMで合計15個存在する

そして、各SMは最大64Warpを切り替えながら同時並列に実行するマルチスレッドプロセサであり、最大では64Warp×32Thread×15SM=30,720スレッドを(サイクルごとの切り替えを含めると)並列的に実行することになる。レベル1キャッシュはSMごとに存在するので、同時並列的に同じレベル1データキャッシュ(L1Dキャッシュ)を使用する最大のスレッド数は32×64=2,048スレッドとなる。このKepler GPUのL1Dキャッシュは最大48KBであるので、1スレッドあたり約24バイトという容量である。GPUの扱うデータサイズは4バイトであるので、この容量ではキャッシュに格納できるのは平均的には6変数/スレッドだけということになる。

GPUの場合は、シェアードメモリを使って処理を行い、グローバルメモリへのアクセスはできるだけ減らすようにプログラムを作り、キャッシュに格納するのはスレッドごとに個別の変数ではなく、複数、できれば32スレッドで共通に使うような変数を格納してやれば、この6変数/スレッドというほど状況は悪くないが、それでもCPUの場合は、1つのコアで並列に走るスレッドはせいぜい4とか8で、各スレッドに8KB程度のキャッシュメモリがあり、スレッドあたり4バイトの変数を2048個格納できるという状況とは大きく異なっている。

このため、L1Dキャッシュは、多くの場合、コアレス用のバッファ(同一キャッシュラインへのWarp内のスレッドのアクセスデータをまとめてから書き込む、あるいは逆に分解して取り出す)という位置づけで使われていることが多く、本来のキャッシュとして力を発揮することは少ないと思われる。

容量が小さいので、Keplerや最新のMaxwell GPUでは、デフォルトではデバイスメモリ上のグローバル変数やローカル変数をL1Dキャッシュでキャッシュすることは行わず、コンパイラがレジスタが不足した場合に一時的にレジスタの内容を退避するSpill/Fill処理を行うために使っている。SpillからFillまでの時間は比較的短いことが多いので、デバイスメモリ(実際はL2キャッシュにヒットする場合がほとんど)よりアクセス時間が短いL1Dキャッシュを使うのは効果的である。普通のローカルやグローバル変数のキャッシュに使えないのはもったい無いという気もするが、所詮、6変数/スレッドの容量しかないのであるから、L1Dキャッシュに多くを期待するのは無理である。

なお、コンパイラオプション(-dlcm=ca)使えば、ローカルやグローバル変数をキャッシュするコードを生成することは可能であるし、NVIDIAの抽象アセンブラであるPTXのld命令をハンドエディットしてld.caとすれば、ロード命令ごとにL1Dキャッシュに入れるかどうかを指定することができる。

また、GPUは貼り紙の模様を読み出すためのTexture Cacheというリードオンリーのキャッシュを持っており、科学術実計算の場合は、このキャッシュを書き換えを行わないリードオンリーキャッシュメモリとして使うことができる。Kepler GPUでは、このキャッシュの容量はSMあたり48KBとなっている。