= Index = [[TitleIndex(CALプログラミング)]] = 簡単なプログラム = 一番簡単なテストプログラムが"/usr/local/amdcal/samples/app/hellocal"(CALをインストールしたディレクトリ)にある。これをコピーして、不必要なものを取り除いたバージョンのプログラムが[attachment:CALTEST.tar.gz]にあるので、これを自分のホームディレクトリに展開する。 makeすることで、"hellocal"というプログラムが作られる。 まずはこのプログラムを実行してみること。何かキーを押したら終了する。 実行例 {{{ gpu1[~/CALTEST] ./hellocal 0.000000 0.500000 1.000000 1.500000 2.000000 2.500000 3.000000 3.500000 128.000000 128.500000 129.000000 129.500000 130.000000 130.500000 131.000000 131.500000 256.000000 256.500000 257.000000 257.500000 258.000000 258.500000 259.000000 259.500000 384.000000 384.500000 385.000000 385.500000 386.000000 386.500000 387.000000 387.500000 512.000000 512.500000 513.000000 513.500000 514.000000 514.500000 515.000000 515.500000 640.000000 640.500000 641.000000 641.500000 642.000000 642.500000 643.000000 643.500000 768.000000 768.500000 769.000000 769.500000 770.000000 770.500000 771.000000 771.500000 896.000000 896.500000 897.000000 897.500000 898.000000 898.500000 899.000000 899.500000 Press enter to exit... }}} = hellocalの解説 = このテストプログラムは、'''入力されたデータに0.5を掛けるという演算'''をGPUでおこなって、結果を得るというプログラムである。 入力データは"hellocal.cpp"の118行の2重ループで値が設定されている変数fdata[]である。102行で、inputResが定義されている。この定義から、inputResが256x256の浮動小数点変数であることがわかる。C言語の配列で書くとfloat array[256][256]になる。116行と117行において、inputResとfdataがマッピングされていることを確認すること。これにより、fdata[]に代入したデータは、GPU上のメモリ(inputRes)に転送されることになる。 同様に、出力データが103行で定義されていて、結果を得るために、190行でfdata[](このポインタ変数は2重の役割を果たしている)にマッピングされている事を確認すること。191行からのループにより、GPU上のメモリ(outputRes)から、結果を回収して出力していることを確認すること。 入力データが256x256であることから、計算に利用されるdomainが決定される(176行にて定義)。domainとは、単純には計算に利用されるGPU上のプロセッサの(論理的な)数だと思えばよい。つまりこの場合、論理的には256x256=65536個のプロセッサにより、65536個の入力データへの演算が並列に実行されると考える。この時に並列に実行されるプログラムをCALでの「kernelプログラム」と呼ぶ。 このプログラムの場合、kernelプログラムは24-33行で定義されている。コピーすると以下の通り(先頭は行番号): {{{ 1 il_ps_2_0 2 dcl_input_interp(linear) v0.xy 3 dcl_output_generic o0 4 dcl_cb cb0[1] 5 dcl_resource_id(0)_type(2d,unnorm)_fmtx(float)_fmty(float)_fmtz(float)_fmtw(float) 6 sample_resource(0)_sampler(0) r0, v0.xyxx 7 mul o0, r0, cb0[0] 8 ret_dyn }}} '''このkernelプログラムが、個々の論理的なプロセッサで並列に(独立に)実行されるというのが、CALのプログラミングモデルである'''(これ重要)。kernelプログラムが実行されるときには、個々のプロセッサに、一意の番号が割り振られる。この番号は、UNIXなどでの"process id"に相当するものである。CALプログラミングにおいては、この番号はdomainの定義に応じて2次元で表される。つまり、このプログラムの場合には、(0,0)から(255,255)までの65536個の番号が割り振られることになる。 このkernelプログラムにおいて、「入力されたデータに0.5を掛けるという演算」は7行で定義されている。CALのILアセンブラは、"命令 出力変数 入力1 入力2"というフォーマットなので、この行の意味は「r0にcb0[0]を掛けてo0に格納する」ことになる。変数cb0[0]は4行で定義されており、これは大きさが1であるcb0という定数配列を定義するという宣言文である。また、変数o0は3行で定義されており、これはこの変数を出力に利用するという宣言文である。 残ったのは変数r0であり、これが入力データに対応する。6行は、他の変数とは違い宣言文ではなく、「変数r0に入力データを読み込む」という意味を持つ。5行目のような宣言文とセットで利用すると覚えること。 さて、問題は、256x256で設定された入力データfdata[]のどのデータが読み込まれるのかということである。実はこれは、6行のふたつめの変数である"v0.xyxx"にて指定されている。変数v0とはなんだろうか?これは2行目で宣言されている。この行は、上で説明した「一意の番号」を"v0.xy"に読み込むということを表している。この2行目の宣言により、1番目のプロセッサではv0.x=0, v0.y=0となり、2番目のプロセッサではv0.x=1, v0.y=0、3番目のプロセッサではv0.x=3, v0.y=0...と続き、65536番目のプロセッサではv0.x=255, v0.y=255という「一意の番号」が個々のプロセッサに割りあてられることになる。 よって、6行目の意味を具体的に書くと「変数r0に入力リソース0(2次元)のv0.xy番地を読み込む」という意味となる。C言語で書き直すと、以下のような意味となる: {{{ r0 = i0[v0.x][v0.y] }}} なお、入力データがなぜ"i0"という変数になるかというと、6行で"resource(0)"となっているから。この変数"i0"はhellocal.cppの165行にあらわれる。これにより、inputResがkernelプログラムの"i0"という変数に対応することがわかる。 変数o0については、なぜかデータが保存される番地が自動的に決定される。実は、その番地はv0.xyと同じである。よって、6行と7行を合わせて、具体的に書くと以下のようなC言語のプログラムとなる(変数は全てfloat変数。ただしcb0については次の説明に注意すること): {{{ o0[v0.x][v0.y] = i0[v0.x][v0.y]*cb0[0] }}} すなわち、変数cb0[0]に0.5がはいっていれば、最初に書いた「入力されたデータに0.5を掛けるという演算」が実現できることになる。それをふまえた上で、hellocal.cppに戻ると、128-138行と166行において変数cb0[0]が定義、マッピングされていることがわかる。細かい点として、136,137行でcb0[0]に転送される定数データを指定しているが、実はkernelプログラムの4行における定数配列宣言は、4要素のベクトル定数の宣言になる。つまり、4行の宣言は、C言語では"cb0[0][4]"に相当する(CAL上では4要素へのアクセスを"xyzw"のmaskにより指定することに注意)。よって、hellocal.cppにおけるconstPtr[]への値の代入は、cb0[0].xyzwの代入に対応する。具体的には以下のようになる: {{{ constPtr[0] -> cb0[0].x constPtr[1] -> cb0[0].y constPtr[2] -> cb0[0].z constPtr[3] -> cb0[0].w }}} 結果として、このプログラムを実行した場合、kernelプログラムのcb0[0]は以下のようになる。 {{{ cb0[0].x = 0.5, cb0[0].y = 0.0, cb0[0].z = 0.0, cb0[0].w = 0.0 }}} = CALプログラミング(1)課題 = * hellocal.cppを変更して「入力されたデータに0.1を掛けるという演算」を実行するようにせよ。 実行例: {{{ gpu1[~/CALTEST] ./hellocal [20:26] 0.000000 0.100000 0.200000 0.300000 0.400000 0.500000 0.600000 0.700000 25.600000 25.700001 25.800001 25.900000 26.000000 26.100000 26.200001 26.300001 51.200001 51.299999 51.400002 51.500000 51.600002 51.700001 51.799999 51.900002 76.800003 76.900002 77.000000 77.099998 77.200005 77.300003 77.400002 77.500000 102.400002 102.500000 102.599998 102.700005 102.800003 102.900002 103.000000 103.099998 128.000000 128.100006 128.199997 128.300003 128.400009 128.500000 128.600006 128.699997 153.600006 153.699997 153.800003 153.900009 154.000000 154.100006 154.199997 154.300003 179.199997 179.300003 179.400009 179.500000 179.600006 179.699997 179.800003 179.900009 Press enter to exit... }}} * hellocal.cppを変更して「入力されたデータに0.1を掛けて、さらに10.0を足すという演算」を実行するようにせよ。 実行例: {{{ gpu1[~/CALTEST] ./hellocal 10.000000 10.100000 10.200000 10.300000 10.400000 10.500000 10.600000 10.700000 35.599998 35.700001 35.799999 35.900002 36.000000 36.100002 36.200001 36.299999 61.200001 61.299999 61.400002 61.500000 61.600002 61.700001 61.799999 61.900002 86.800003 86.900002 87.000000 87.099998 87.200005 87.300003 87.400002 87.500000 112.400002 112.500000 112.599998 112.700005 112.800003 112.900002 113.000000 113.099998 138.000000 138.100006 138.199997 138.300003 138.400009 138.500000 138.600006 138.699997 163.600006 163.699997 163.800003 163.900009 164.000000 164.100006 164.199997 164.300003 189.199997 189.300003 189.400009 189.500000 189.600006 189.699997 189.800003 189.900009 Press enter to exit... }}}