シェアードメモリ

シェアードメモリ(SMEM)は物理的にはL1キャッシュと同じSRAMアレイを分割して使っており、DRAMにアクセスするのと比べると20~30倍高速である。また、バンド幅もDRAMの10倍となっている。そして、KeplerのSMEMは8B単位でアクセスができ、DRAMが最低でも32Bにアクセスであるのと比べて小回りが利く。

シェアードメモリ(SMEM)はL1キャッシュと同様に高速でアクセスができ、バンド幅もDRAMよりも10倍大きい。そして8Bという細かい単位でアクセスができる (2)

KeplerのSMEMはアクセスのデータ幅が8Bのバンクが32個あるという構成になっている。前に述べたように、1つのワープでロード、ストア命令を実行すると、32のメモリアクセスが必要になる。この32のアクセスが32のバンクに1対1に対応するアドレスである場合は1回のSMEMアクセスで済む。また、次の図のようにロード命令が4B単位の場合は、同じバンクの同じ8Bに別のスレッドからのアクセスが行われる場合もバンクのぶつかり(Bank Conflict)がなく1回のSMEMアクセスで処理できる。

SMEMへのアクセスの例。バンク0の1行目の上位4Bと下位4Bへのアクセス、バンク2の同一データへの2スレッドのアクセスなどはコンフリクトとはならない (2)

しかし、同じバンクへN個アクセスが集中し、そのアドレスが異なる場合は1回のSMEMアクセスでは処理できず、N-1回のreplayが必要となる。

この例ではバンク1に4つのアクセスが重なり、3つが同一バンクの異なる行のアクセスなのでコンフリクトになる。この例では最初の1回のアクセスに加えて2回のreplayが必要となる (2)

また、複数のスレッドが同じアドレスを読む場合は、SMEMからのデータの読み込みは1回で、要求したすべてのスレッドにデータを渡すマルチキャスト機能をサポートしている。なお、複数のスレッドが同じアドレスに書き込む場合は、どれか1つの書き込みが有効になるが、どれが有効になるかは不定である。

アドレスとバンクの対応は、Keplerの8バイトモードでは、8B(4Bデータ2個分)を単位としてバンク0~31に巡回して割り当てられる。つまり、アドレスの下位8ビットを…BBBBBxxxとすると、xxxが8バイトの中のバイト位置を表し、BBBBBがバンク番号となっている。

Keplerの8バイトモードでは、4Bのブロック0、1がバンク0、ブロック2、3がバンク1、…,ブロック62、63がバンク31に割り当てられ、ブロック64、65は元に戻ってバンク0というように割り付けられる (2)

なお、Fermiのバンクのデータ幅は4Bであり、KeplerはFermiとの互換性を維持する4Bモードのアクセスを行うことができるが、これについては説明を省略する。

ケース3は、倍精度浮動小数点の行列の転置である。32行32列分のSMEMを確保し、ここにGMEMから読んだ行列のデータを格納する。そしてSMEMから1列分のデータを読み出してGMEMの行に書き出すことで転置を行う。このため、32×32のスレッドブロックを使う。

SMEMへの行列の格納は、1行が横方向にバンク0からバンク31に格納される。これを列順に読む場合は、最初はすべてのスレッドが列0(緑)を読むので、32Wayのコンフリクトが発生する。2回目はすべてのスレッドが列1(赤)を読みというように、毎回、32Wayコンフリクトが発生する (2)

このやり方では、列のアクセスごとにすべてのスレッドが同一のバンクの異なる行をアクセスし、32Wayのコンフリクトが発生する。そうなるとreplayを含めて32×32回のSMEMアクセスが必要となり、長い時間がかかることになる。

これに対して、SMEM上に1列分のpaddingを付け、32行33列分の領域を確保すると、SMEMへの格納状況と読み出し状況は次の図のようになる。

SMEMの領域をPaddingを含めて33列分確保すると、1行目のPaddingがバンク0となり、2行目の列0の要素はバンク1となる。そして3行目の列0はバンク2、…となる。結果として、列0の要素は、バンク0、1、2、…に格納される。このため、列0の読み出しにはバンクコンフリクトは発生しない。また、他の列の読み出しも同様である (2)

このようにpaddingを付けることで列の読み出し時のバンクコンフリクトが無くなり、結果として約2倍に性能が向上するという。