41 | | * このテストプログラムは、入力されたデータに0.5を掛けるという演算をGPUでおこなって、結果を得るというプログラムである。 |
| 43 | * 入力データは"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)から、結果を回収して出力していることを確認すること。 |
| 44 | |
| 45 | * 入力データが256x256であることから、計算に利用されるdomainが決定される(176行にて定義)。domainとは、単純には計算に利用されるGPU上のプロセッサの(論理的な)数だと思えばよい。つまりこの場合、論理的には256x256=65536個のプロセッサにより、65536個の入力データへの演算が並列に実行されると考える。この時に並列に実行されるプログラムをCALでの「kernelプログラム」と呼ぶ。 |
| 46 | |
| 47 | * このプログラムの場合、kernelプログラムは24-33行で定義されている。コピーすると以下の通り(先頭は行番号): |
| 48 | {{{ |
| 49 | 1 il_ps_2_0 |
| 50 | 2 dcl_input_interp(linear) v0.xy |
| 51 | 3 dcl_output_generic o0 |
| 52 | 4 dcl_cb cb0[1] |
| 53 | 5 dcl_resource_id(0)_type(2d,unnorm)_fmtx(float)_fmty(float)_fmtz(float)_fmtw(float) |
| 54 | 6 sample_resource(0)_sampler(0) r0, v0.xyxx |
| 55 | 7 mul o0, r0, cb0[0] |
| 56 | 8 ret_dyn |
| 57 | }}} |
| 58 | |
| 59 | * '''このkernelプログラムが、個々の論理的なプロセッサで並列に(独立に)実行されるというのが、CALのプログラミングモデルである'''(これ重要)。kernelプログラムが実行されるときには、個々のプロセッサに、一意の番号が割り振られる。この番号は、UNIXなどでの"process id"に相当するものである。CALプログラミングにおいては、この番号はdomainの定義に応じて2次元で表される。つまり、このプログラムの場合には、(0,0)から(255,255)までの65536個の番号が割り振られることになる。 |
| 60 | |
| 61 | * このkernelプログラムにおいて、「入力されたデータに0.5を掛けるという演算」は7行で定義されている。CALのILアセンブラは、"命令 出力変数 入力1 入力2"というフォーマットなので、この行の意味は「r0にcb0[0]を掛けてo0に格納する」ことになる。変数cb0[0]は4行で定義されており、これは大きさが1であるcb0という定数配列を定義するという宣言文である。また、変数o0は3行で定義されており、これはこの変数を出力に利用するという宣言文である。 |
| 62 | |
| 63 | * 残ったのは変数r0であり、これが入力データに対応する。6行は、他の変数とは違い宣言文ではなく、「変数r0に入力データを読み込む」という意味を持つ。5行目のような宣言文とセットで利用すると覚えること。 |
| 64 | |
| 65 | * さて、問題は、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という「一意の番号」が個々のプロセッサに割りあてられることになる。 |
| 66 | |
| 67 | * よって、6行目の意味を具体的に書くと「変数r0に入力リソース0(2次元)のv0.xy番地を読み込む」という意味となる。C言語で書き直すと、以下のような意味となる: |
| 68 | {{{ |
| 69 | r0 = i0[v0.x][v0.y] |
| 70 | }}} |
| 71 | |
| 72 | * なお、入力データがなぜ"i0"という変数になるかというと、6行で"resource(0)"となっているから。この変数"i0"はhellocal.cppの165行にあらわれる。これにより、inputResがkernelプログラムの"i0"という変数に対応することがわかる。 |
| 73 | |
| 74 | * 変数o0については、なぜかデータが保存される番地が自動的に決定される。実は、その番地はv0.xyと同じである。よって、6行と7行を合わせて、具体的に書くと以下のようなC言語のプログラムとなる(変数は全てfloat変数。ただしcb0については次の説明に注意すること): |
| 75 | {{{ |
| 76 | o0[v0.x][v0.y] = i0[v0.x][v0.y]*cb0[0] |
| 77 | }}} |
| 78 | |
| 79 | * すなわち、変数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の代入に対応する。具体的には以下のようになる: |
| 80 | {{{ |
| 81 | constPtr[0] -> cb0[0].x |
| 82 | constPtr[1] -> cb0[0].y |
| 83 | constPtr[2] -> cb0[0].z |
| 84 | constPtr[3] -> cb0[0].w |
| 85 | }}} |
| 86 | |
| 87 | * 結果として、このプログラムを実行した場合、kernelプログラムのcb0[0]は以下のようになる。 |
| 88 | {{{ |
| 89 | cb0[0].x = 0.5, cb0[0].y = 0.0, cb0[0].z = 0.0, cb0[0].w = 0.0 |
| 90 | }}} |