バグがとれないときは、最低限の再現ケースを作成しましょう。
ほぼ最低限だけ記述した状態で再現するから困るんだよね、こういうのって。
バグがとれないときは、最低限の再現ケースを作成しましょう。
ほぼ最低限だけ記述した状態で再現するから困るんだよね、こういうのって。
リファレンスを読み直したら、Warpの概念を間違えていた気がしてきた。というか考え直していたら元々どう考えていたかを忘れた。
だめじゃん。
先日の学会のときにCUDAをアプリケーション高速化側で使っている人たちがいたので話をしたんだが、彼らはいわゆるCUDAのアセンブラを眺めて最適化具合をチェックしていたらしい。
つーわけで、あえてスルーしていたアセンブラコードを眺めてみたんだが……
……うーん、これを眺めてチェックするのは正直勘弁してほしいなぁ。別に読めないわけではないけど……まぁある意味これさえ使えればある意味でやりたい放題できるから、意味はあるのかな(何
なんか計算と通信のオーバーラップができるようになったらしい。ということをドキュメントで読んだので研究情報垂れ流しblogに書いた。
んで、詳細を調べようと思って適当なキーワードでぐぐったら、さっき(二時間前)書いた自分の記事がTOPだった。
情報量0じゃねーか。オノレ。
CUDAにはデバイスエミュレーションモードがあるのでCUDA対応GPUがなくてもプログラムの実験ができます。というお話。
無茶をするなと。
以下導入メモ:
というわけで無事実行に成功。
DirectXのREFみたいにnativeとemulationでできることできないことに差が生じる可能性は否定しないけど、その辺は追々ということで。
__device__なclassを作るとどうなるかというお話。
普通に使えたらかなり便利じゃね?
とりあえず、
なんてのを用意しておいてみる。
でどうか。
結果は、hoge.func()とhoge.func2()の両方で
"cuda.cu", line 78: error: calling a host function from a
__device__/__global__ function is only allowed in device emulation
mode
value[0] = hoge.func() - hoge.func2();//value;
^
"cuda.cu", line 78: error: calling a host function from a
__device__/__global__ function is only allowed in device emulation
mode
value[0] = hoge.func() - hoge.func2();//value;
^
2 errors detected in the compilation of "/tmp/tmpxft_00007432_00000000-3.ii".
となり、関数が呼べない。まぁそりゃそうだろう。
ちょっと問題を縮小し、最低限の記述に落としてみる。
これでも同じエラー。
これだとうまく動いて-1が得られる。なるほど。
ちょっと趣向を変えて。
……あれ?classの実体宣言には__device__つけていないんだけど、いいのかなぁ?
classに変数を持たせてみる。
すると、
"cuda.cu", line 62: error: identifier "hoge1" is undefined
(hoge1.value) = 10;
^
"cuda.cu", line 63: error: identifier "hoge2" is undefined
(hoge2.value) = 20;
^
"cuda.cu", line 64: error: identifier "hoge1" is undefined
(value[0]) = (((hoge1.value)) + ((hoge2.value)));
^
"cuda.cu", line 64: error: identifier "hoge2" is undefined
(value[0]) = (((hoge1.value)) + ((hoge2.value)));
^
4 errors detected in the compilation of "/tmp/tmpxft_000074c1_00000000-4.i".
……あれ?
に変更したら、今度は
"cuda.cu", line 62: warning: expression has no effect
(hoge1.value; }
^
"cuda.cu", line 63: warning: expression has no effect
(hoge2.value; }
^
というwarnigのみになり、実行結果も30と正しそうな感じに。
まぁなんだ、要するにインライン展開されまくりなんだろうな、きっと。
とりあえず関数も持てる構造体、程度には使えると。
(ええと、C++の場合は構造体に関数持たせられるんだっけ?そういえば。)
次。
class CHoge
にコンストラクタやらデストラクタやらを追加してみる。
これで
"/usr/lib/gcc/i386-redhat-linux/4.1.1/../../../../include/c++/4.1.1/cstdlib", line 178: internal error:
can't generate code for non empty constructors or destructors on
device
div(long long __n, long long __d)
^
1 catastrophic error detected in the compilation of "/tmp/tmpxft_00007555_00000000-3.ii".
Compilation aborted.
意味がわからんエラーが出る。まぁコンストラクタやデストラクタはダメだよって言われてるわけだな。
残念ながらコンストラクタを__device__にしても状況は変わらないし、int value;を__device__にしても
"cuda.cu", line 45: error: memory qualifier on data member is not allowed
__attribute__((__device__)) int value;
^
とかエラーが変わるのみ。
うーん。
流石にまともにclassらしくclassを活用することはできなさそうだ。ちょっと残念。
それにコンストラクタとかが使えないのは微妙に不便だ。
コンストラクタっぽいことをする妥当なアイディアとしては、classを__device__に持っていることが前提だけど、初期化用の__device__関数を利用するのがいいのかな。
それにしてもなんだこのグダグダのエントリ。まとまりわりい。
変数idを__shared__にすると思い通りに動くよ!
い み が 。
アレな状況に陥ったので報告。本家forumで議論してきたほうがいいかもシレン。
grid(2,1,1)な実行後のvalueは何になるか。
答えは
value[0] = 0
value[1] = 0
……いや、それはおかしいだろう。
実は、例えば以下のように書くと想定どおりの答えが得られる。
これで答えは
value[0] = 1
value[1] = 2
となる。
でもいいや。
なんてのもありらしい。
あくまで予想なんだけど、ifブロックの中で変数idが使われていないという理由で、コンパイラがid変数を最適化の課程で殺しているんじゃないだろうか。だとしたらコンパイラのバグだよなぁ……。
やっぱ本家にゴルァかな?勘違いかもしれないので一日熟成させよう。
このへんの続き。
前回は何故か__syncthreads()を使ったりと妙にキモイことをやっていたので、追実験。
atomic関数の存在も念頭に置いてみる。
まぁこんなケースを考えることにする。
んで同期をどうやるか。
とりあえず考えたのが以下の実装。
atomicAddで加算の保証が出来るので、あとは全blockが加算するのを待てばいいんじゃないかと。
うん。失敗した。無限ループっぽい。
直感的にはこれで行けそうなんだけど、何かひっかかるらしい。
という感じに__syncthreads();を入れると正しく動作する。
__syncthread()にそれらしい仕様はない気がするんだが……?
ところで、atomicAddは確実に読み出して加算ができる、ってのはいいんだけど、その処理の途中で別のblock/threadが値を読んでしまうことってあるんだろうか。いや、別に「0が読めるか1が読めるかわからないよ」ってのは構わないというか当然なんだけど、何故か全然違う値が読めたりすると危険だよなぁ……。
さて、胡散臭い__syncthreads()を使わずにちゃんとやるにはどうすればいいかなんだが、作業用の変数を追加して
とやったらうまくいった。
安全を確保するならwaitflagの変更はatomicの方がいいのかもしれないけど、まぁ大丈夫だろう。
さて。以上の振る舞いから、今回のglobal memory spaceの変数の参照はコンパイラによって最適化されてしまっているのではないかという仮説が立てられる。
nvccの-ptxや-keepで中間コードが覗けるんだけど、これ見て判断するのはちょっとツライんだよなぁ。
最後。
仮設を踏まえて適当にうまく行く記述を考えてみた。
明示的にglobal memory spaceからの再読み込みを行うような感じ。
実際、これで思い通りの動作は出来ている。
ちなみに、どの実装にしても複数回の実行にはちょっとだけ工夫が必要かもしれない。
作業用変数のクリアが必要だから。
とはいえ、作業用変数を多重化して、同期が終了するときに今使ったものとは別の作業用変数を初期化してやれば動きそうだから多分大丈夫だろう。たぶん。
以下、#ifで切ったプログラム全体。長いけど参考にベタッと貼っておく。