wiki:CALプログラミング(3)

Index

ループの例

何か役立つ計算をおこなうためには、ループ処理が必須となる。CALでループ処理を実装してみよう。

CALTEST3.tar.gzにループを使った計算例のプログラムを置いたので、自分のディレクトリにコピーして実行すること。

gpu1[~/CALTEST3] ./hellocal
55.000000 55.000000 55.000000 55.000000 55.000000 55.000000 55.000000 55.000000
55.000000 55.000000 55.000000 55.000000 55.000000 55.000000 55.000000 55.000000

Press enter to exit...

このプログラムは、1から10までの合計を計算するプログラムである。

なお、このプログラムから、kernelプログラムは別ファイルとして処理している。"hellocal.cpp"の34-51行にて、"prog.il"というファイルを読み込んでいる。kernelプログラムのみに変更を加える場合には、このファイルを変更するだけでよくて、再度makeする必要はない。

"hellocal.cpp"の本体部分は、これまでのプログラムとほとんど変わりがない。入出力用の配列を2次元から1次元としたことが大きな違いである。69-70行で、"calResAllocLocal1D()"によって1次元のメモリを確保している。この入力用のメモリには、78-84行にて1からnx(=256)の数字を代入している。

計算domainは(0, 0, 256, 1)としている(109行)ので、256個の論理プロセッサにて"prog.il"が実行される。

kernelプログラムは、前と比べるとかなり変更がある。重要な変更点は:

  1. "dcl_literal"による定数値の宣言と利用
  2. "whileloop"によるループ処理の記述
  3. データを読み込むポインタの更新

以下、個々の点について詳しく説明する。

定数について

wiki:"CALプログラミング(1)"のプログラムでは、浮動小数点整数をホストプログラムから転送していたが、CALでは定数値を直接プログラム中で利用することもできる。今回のプログラムでは"l0"と"l1"という定数値が5-6行で定義されている。

dcl_literal 変数名, xの値, yの値, zの値, wの値

というように宣言する。4要素を持つ変数であることに注意すること。整数を書けばそのままであり、"0xXX"とすれば16進数になるし、小数点が含まれる数は浮動小数点として扱われる。

5-6行での宣言により、CAL上では以下のような定数として、"l0"と"l1"を利用できる。

l0.x = 0.0f, l0.y = 0.0f, l0.z = 1.0f, l0.w = 0.0f
l1.x = 0, l1.y = 0, l1.z = 1, l1.w = 10

l0は、8行において、r1.xyへの代入で利用されている。結果として

r1.x = 0.0, r1.y = 0.0

となる。l1も同様に使われている。

実際には、これらの数字には以下のような意味がある:

l0.xとl0.y データの読み込みのポインタ初期値
l0.z そのポインタのインクリメント用
l1.x ループカウンタの初期値
l1.z   ループカウンタのインクリメント用

これらはループ処理とデータの読み込みに利用される。詳細は以下で説明する。

ループについて

CALでのループの実現には、色々な方法があるが、ここでは"whileloop"文を利用する。これは、"whileloop"と"endloop"に挟まれた命令列を永久に実行するという命令文である。よって、無限ループを終わらせる処理を自前で書く必要がある。必要な部分だけを抜き出すと以下のようなCALプログラムがループのひな形となる:

whileloop
  ige r2.x, r4.x, r4.w
  break_logicalnz r2.x
  iadd r4.x, r4.x, r4.z
endloop

ここで、r4.xをループカウンタとして、r4.wをループの上限値として使っている。これはC言語で書くと以下のような処理に相当する:

while(1) {
  if (r4.x >= r4.w) break;
  r4.x = r4.x + 1;
}

"ige"の行は、r4.xとr4.wの大小を比較して、"r4.x >= r4.w"が成り立っているならば、r2.xにTRUEがセットされる(詳しくはil.pdfの43ページを参照のこと。以下同様)。

"break_logicalnz r2.x"は、r2.xが0でないならば、今のループを終了する。TRUEは0ではないので、"ige"でTRUEがセットされていたら、このループが終了する。この2命令を合わせて、C言語のほうのif文に相当することになる。

"iadd"文は、単純に整数の加算である。r4.zは整数値1として初期化されているので、r4.xをインクリメントすることになる。

配列データのランダムアクセス

これまで、データの読み込みには、"dcl_input_interp"命令で得たindex pair(v0.xy)を使ってきた。これは、kernelプログラムが実行される論理プロセッサごとに、別々のindex pairが割り振られる。実際には、index pairの値は(0.0, 0.0)からはじまる浮動小数点値の組である(と思われる)。 今のプログラムにおいて、id=0のリソースは、CAL_FORMAT_FLOAT_1で1次元のメモリとして宣言したので、C言語の配列で書くとすると、"float array[256]"と同等である。このリソースの任意の場所をCALのプログラムで読み込むためには、"sample_resource"命令に適切なindex pairを与えればよい。具体的には以下のようになる:

array[0] -> (0.0, 0.0)
array[1] -> (1.0, 0.0)
array[2] -> (2.0, 0.0)
 ...
array[254] -> (254,0, 0.0)
array[255] -> (255.0, 0.0)

実際には、今の場合リソースが1次元として宣言されているのでyのほうの値は無視される。

"prog.il"の16行では、r1.xがindexとして指定されている。この値は8行で0.0に初期化されており、19行で1.0が足されている。よって、このループで19行が実行される度に、r1.xは0.0,1.0, 2.0...と増加していくことになる。結果として、r1.xで指定された場所から、r0.xにid = 0のリソースから値が読み込まれることになる。読み込まれた値は、17行においてr3.xと足し合わされ、合計が計算される。

動作のまとめ

以上の動作をまとめると、このkernelプログラムは「id=0のリソースを先頭から順番に10回読み込んでその合計を計算する」という動作になる。この処理が、論理的なプロセッサ上で並列に実行される。

なお、ループ回数の10は、"prog.il"の6行のw成分で指定されている。今、"hello.cpp"では、この入力メモリに1から256までの値をいれているので、計算結果は55となる。

課題

  • ループ回数を"hello.cpp"から設定できるように変更せよ

  • さらに以下のCプログラムの配列sum[]を計算するように変更せよ。入力配列をarray[]とし、ループ回数をnとしたとき:
     for(i = 0; i < n; i++) {
       result = 0.0;
       for(j = 0; j < n; j++) {
         result += (array[j] - array[i])*(array[j] - array[i]);
       }
       sum[i] = result;
     }
    
Last modified 15 years ago Last modified on Mar 22, 2009 1:24:35 PM

Attachments (1)

Download all attachments as: .zip