CUDAの話です。
先日CUDAのコードを読んでいると、不可解なコードに行き当たりました。
energygrid[outaddr] += energyvalx1;
energygrid[outaddr + 1 * BLOCKSIZEX] += energyvalx2;
energygrid[outaddr + 2 * BLOCKSIZEX] += energyvalx3;
...
energygrid[outaddr + 6 * BLOCKSIZEX] += energyvalx7;
energygrid[outaddr + 7 * BLOCKSIZEX] += energyvalx8;
これは配列energygrid[]に、floatの値を足しているようです。
それはいいのですが、インデックスに注目してください。
1 * BLOCKSIZEXとはなんでしょう?
2 * BLOCKSIZEXとは?
調べてみると、BLOCKSIZEXは16でした。
つまり言い換えると次のようにアクセスしていることになります。
energygrid[outaddr] += energyvalx1;
energygrid[outaddr + 16] += energyvalx2;
energygrid[outaddr + 32] += energyvalx3;
...
energygrid[outaddr + 96] += energyvalx7;
energygrid[outaddr + 112] += energyvalx8;
……
ちょっと待ってください。
なぜ16飛び飛びにアクセスしているのでしょうか?
このコードの元の目的から考えると(ここでは解説しませんが)、プログラムはenergygridすべての要素にアクセスします。
というか全要素にアクセスしなければなりません。
全要素にアクセスするなら飛び飛びアクセスでなくてもいいんじゃ!?
単に順番に、[outaddr]、[outaddr + 1]、[outaddr + 2]、...[outaddr + 7]というふうにアクセスしてはなぜいけないのでしょう?
しばらく頭を捻った後ようやくわかりました。
つまりこれは複数のスレッドで考えると話が見えてくるのです。
今までは一つのスレッドでこう考えていました:
こう考えるとたしかに不可解なのです。
インデックスが16飛び飛びですから。
「どうして0,1,2,3じゃなくて0, 16, 32, 48なの?」となるわけです。
しかしCUDAはGPGPU。
スレッドは多数用意して計算するもの。
そうすると実行時には次のようになります:
おわかりいただけたでしょうか…
一つのスレッドで考えるとたしかに飛び飛びだったものが、複数スレッドで見るとアクセスが綺麗に連続していることがわかります。
全部のスレッドの最初のアクセスは、0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15と連続しています。
そのつぎもやはり連続しています。
CUDAではこのように複数のスレッドの固まりが連続したところにアクセスしていると、そのメモリアクセスをまとめ(Coalescing)てくれるそうなのです。
こうしてまとめられたメモリアクセスは、そうでないものより効率がいいそうです。
だからわざわざ*16していたのですね。
ちなみに16というのは、半ワープというもののサイズです。
CUDAでは実行するスレッドはワープという固まりをなします。
それが32個です。
1ワープ32スレッド。
半ワープというのはその半分の16個です。
メモリアクセスのまとめ(Coalescing)は、半ワープが単位となっているそうです。
なお、GPU使っているのにスレッドが16個しか出来ないの?と思われる方もいらっしゃるでしょうが、
実際にはそれより多いです。
上のプログラムでは16より多い数のスレッドを起動していました。
スレッド16 ~ 31やそれ以降は、また別のまとめ(Coalescing)をしているということです。