9. バンクコンフリクトを解消する

バンクコンフリクトを避けるにはどうすれば良いか? それは意外と簡単で、次の図のように各行にtile[*][4]という余計な要素を1つ付け加えてやれば良い。

各行に1つ余計な要素を加える

そうすると、次の図で黄色の字で示したようにtile[0][1]はバンク1、tile[1][1]はバンク2、tile[2][1]はバンク3、tile[3][1]はバンク0というように、列方向の要素が異なるバンクに分かれてくれる。従って、これらの列方向の要素をアクセスする命令を実行してもバンクコンフリクトは発生しない。

余計な要素を1つ加えると、列方向のアクセスが異なるバンクに分かれバンクコンフリクトが発生しない

シェアードメモリのバンクコンフリクトを避けるためには、内側の次元に1つ要素(この例ではtile[*][4])を追加する。このtile[*][4]は格納位置をずらすために追加されており、データは格納しないのでメモリとしては無駄になっているが、行方向も列方向もバンクコンフリクトなしにアクセスできるようになり、シェアードメモリのバンド幅を最大限有効に使えるようにしている。

追加の要素はメモリとしては無駄になっているが、行方向、列方向のアクセスともにバンクコンフリクトを無くし、バンド幅を最大化している

なお、この手法はGPUに特有のものではなく、GPUコンピューティング以前のCPUでも使われている手法である。

シェアードメモリの配列tileの各行に1要素を追加してバンクコンフリクトを解消したtranspose4の性能は、単精度では171.82GB/s、倍精度では225.68GB/sとなった。これはtranspose3と比べて単精度では1.7倍、倍精度では1.76倍の性能である。

バンクコンフリクトの解消で、transpose3と比べて、Floatでは1.7倍、Doubleでは1.76倍に性能が向上

transpose3ではFloatに比べてDoubleの方が性能が低かったが、バンクコンフリクトを無くしたtranspose4ではDoubleの方が性能が高くなっている。この理由は、どちらの場合も1つのワープの32のアクセスは完全に合体しており1回のメモリアクセスで処理されている。この点はどちらも同じであるが、Floatの場合は1つのキャッシュラインしか使われず、DRAMからの読み込みは4つのDRAMトランザクションであるが、Doubleの場合は2つのキャッシュラインへの読み込みが並行して行われ、8つのDRAMトランザクションが使われている点が異なる。

単精度では4 DRAMトランザクションであるが、倍精度の場合は8 DRAMトランザクションとなっている

10.DRAMバンド幅をフルに使う

DRAMからの読み込みトランザクションを2倍に増やせば、単精度の場合も倍精度と同じ程度の性能が得られると考えられるので、1スレッドが処理する要素数を倍増して性能を調べたのが次の図の表である。

スレッドあたりのエレメント数を可変にしたtranspose5で、エレメント数を2にすると、確かにtranspose4の倍精度のバンド幅とほぼ同等の性能が得られた。また、単精度で4エレメントとすると、2エレメントの倍精度と同程度の性能が得られ、DRAMアクセスのバンド幅が性能を決めていることが分かる。

なお、16エレメント/スレッドの場合は、スレッド数が少なくなりすぎて実行するスレッドが足りなくなってしまったことで性能が下がっている。

DRAMトランザクションを同じ数にすると、単精度でも倍精度とほぼ同じ性能が得られることが実証された

複数エレメントを処理するtranspose5プログラムの性能は、Floatでは239.93GB/s、Doubleでは246.65GB/sに達した。これは最初のtranspose1プログラムと比較すると、それぞれ14.15倍と7.27倍の性能向上である。

また、K40のピークメモリバンド幅は288GB/sである。しかし、制御オーバヘッドなどを考慮しないデータ転送のスピードであり、実プログラムでは絶対に達成できない値である。行列の転置という処理で85.6%の性能を達成しているのは驚異的である。

複数エレメントを処理するtranspose5プログラムでは、Floatで239.93GB/s、Doubleでは246.65GB/sを達成

まとめ

このチュートリアルで、覚えて帰るべきことは、

  • データの再利用(ops/byte)を大きくしなければ、オペランドの供給がネックになる
    メモリのバンド幅とレーテンシ
  • バンド幅をフルに利用するには多くのin-flightのロードが必要
  • GPUのメモリを使用する場合は
    アドレスの合体
    ・Load/Storeをまとめるのにシェアードメモリを使う
    レーテンシを隠すには
    ・占有率をあげる
    ・命令レベルの並列性

である。

このチュートリアルで覚えて帰るべきことのまとめ