SMのレジスタとシェアードメモリ資源

GK110チップのSMは、32bitのデータを格納する65,536エントリのレジスタファイルを持っている。CPUの汎用レジスタは16個とか32個と少なく、それに比べると65,536個のGPUのレジスタ数は非常に大きい数である。しかし、このレジスタファイルは、そのSMで並列に実行されるすべてのスレッドで共用されるので、SMの最大の64ワープ×32スレッド=2048スレッドを並列に実行する場合は、1スレッドあたりのレジスタ数は32個となり、スレッドあたりで見ると使えるレジスタ数は汎用CPUのレジスタ数と大差ない数となる。しかし、Kepler GPUのスレッドは最大255個のレジスタエントリを使用することができるので、他のスレッドがあまりレジスタを必要とせずレジスタに余裕がある場合は、多数のレジスタを使って実行性能を改善するというプログラムを書く余地がある。

SMは合計64KBのSRAMを持ち、これをシェアードメモリとL1キャッシュに分割して使用することができるようになっている。この分割は、48KB:16KB、32KB:32KB、16KB:48KBの3通りの分割が可能である。

CUDAプログラムが機械命令に翻訳されると、各スレッドはレジスタを何個必要とするか、シェアードメモリをどれだけ使用するかが決まる。ギガスレッドエンジンがスレッドブロックをSMに割り当てる時点で、スレッドブロックに含まれるすべてのスレッドの実行に必要なレジスタ数とシェアードメモリ量と、そのSMの未割当のレジスタ数とシェアードメモリ量を比較し、そのスレッドブロックが必要とする資源が割り当て可能であれば、スレッドブロックを発行する。SMに資源が残っている場合は、別のスレッドブロックを割り当て、1つのSMに複数のスレッドブロックの実行を割り当てていく。

一方、資源不足の場合は、実行中のスレッドブロックが終わり、資源が解放されて必要な資源が確保できるようになるまで、そのスレッドブロックの発行は待たされることになる。

1つのスレッドが多数のレジスタエントリを使ってしまうと、threadsPerBlockを小さくしないとスレッドブロックがSMに収まらなくなってしまう。また、複数のスレッドブロックを1つのSMに詰め込むために使用レジスタ数を制限したいという場合があり、この場合は、CUDAコンパイラのオプションで、最大使用レジスタ数を指定することができる。このようにして使用レジスタ数を減らすと、使用頻度の低いレジスタをL1キャッシュに追い出して空きを作り、そのデータが必要となった時点でL1キャッシュからレジスタファイルに書き戻すという操作が必要になる。

CUDAの変数定義に"_shared_"を指定すると、その変数はシェアードメモリに割り付けられ、スレッドブロック内の全スレッドから高速のアクセスが可能になる。しかし、シェアードメモリを48KBとしても4B(32bit)の変数にして12K個分であり、2次元配列などを入れようとするとすぐ一杯になってしまうので、限られた容量の高速メモリをうまく使うようにプログラムを書くことが重要である。

Keplerの前世代のFermiでL1キャッシュが導入され、これを使うと自動的にキャッシングされ、シェアードメモリの容量のようなサイズの制限を意識しないでも性能を上げられるという期待があり、L1キャッシュの使用で性能が上がった例も報告されたのであるが、一転してKeplerでは、L1キャッシュの使用が制限されることになった。

汎用のCPUではL1キャッシュは16KB~64KBという容量で、せいぜい数スレッドで共用するのが一般的である。しかし、KeplerのL1キャッシュは最大でも48KBで、64ワープの場合はスレッドあたりのキャッシュはわずか24Bと汎用CPUの1/100という少ない量である。このため、汎用的なデータキャッシュとして使うのには力不足で、Kepler GPUでは、L1キャッシュは汎用レジスタの退避、復元やバッファなどコンパイラが生成し、ローカルメモリ(LMEM)に割り当てた変数に限定して使い、ユーザがCUDAプログラムで定義した一般の変数はL1キャッシュを使わずL2キャッシュから直接、レジスタに出し入れするという構造になった。

また、KeplerのL1キャッシュは、FermiのL1キャッシュと同様に、SM間のキャッシュコヒーレンシを維持するハードウェアを持っていないと考えられる。このL1キャッシュを一般的なデータキャッシュとして使用する場合はソフトウェアによりキャッシュコヒーレンシを実現する必要があるが、今回明らかとなったKeplerのL1キャッシュの使用法であれば、他のSMのL1キャッシュとのコヒーレンシの維持は必要ないので、好都合という考慮もあったのではないかと思われる。

いずれにしても、16~48KBという小容量のL1キャッシュを数100のスレッドが共用し、1.5MBのL2キャッシュも数1000のスレッドが共用するので、スレッドあたりでは非常に少ないキャッシュ容量しかなく、CPUのキャッシュのようには使えないとのことである。