Paraisoの境界条件
直胞機械のshift
命令は配列変数全体を平行移動する命令です。実際のコードが扱う配列変数は常に有限サイズですから、平行移動によりはみ出す部分の処理を境界条件により指定してやる必要があります。Paraisoは、データフローグラフの中のshift
命令を解析して、境界条件を実現するのに必要な処理(袖領域など)を追加してくれます。
Paraisoでは、今のところ周期境界と自由境界という二種類の境界条件をサポートしています。
コード生成時、次元ごとにいづれかの境界条件を選ぶことができます。この二種類とほかの命令を組み合わて作れる範囲で、より複雑な境界条件も扱えます。境界条件は、Setup
データ型のboundary
レコードで、Condition
型を要素にもつベクトルとして指定します。
二種類の境界条件のうち、周期境界は、単に右端からはみ出したアクセスは左端のものを見るだけです。いわゆるドラクエ地図です。
これに対し、自由境界の場合は少々複雑です。まず、袖領域は、どのParaisoカーネルを1ステップ実行しても、
Setupで指定した計算したい領域、添字が0以上localSize
未満の領域が埋めるのに必要十分なサイズをもって決まります。
次に、各カーネルの計算する範囲は、袖領域をふくめた最大の領域から開始し、なるべく大きな領域を埋めるように決まります。その領域の外側での値は未定義です。また、Reduce
演算はつねに袖領域をふくむ全体を畳み込みます。もし袖領域を除外したい場合は手動でマスクする必要があります。
このサンプルプログラムで境界条件の挙動を確かめてみましょう。このプログラムには3つのカーネルがあります。
myKernels :: [Named (Builder Vec1 Int Annotation ())] myKernels = ["init" `isNameOf` do store table $ loadIndex (Axis 0) ,"increment" `isNameOf` do store table $ 1 + load table ,"calculate" `isNameOf` do center <- bind $ load table right <- bind $ shift (Vec :~ (-1)) center left <- bind $ shift (Vec :~ ( 1)) center ret <- bind $ 10000 * left + 100 * center + right store table ret store total $ reduce Reduce.Sum ret ]
それぞれ、「配列の内容を配列添字で初期化する」「配列の内容に1を加える」「ひとつ右とひとつ左の内容を読んで値を計算する」カーネルです。
maker.init();
dump();
maker.increment();
dump();
maker.calculate();
dump();
cout << "total: " << maker.total() << endl;
cout << endl;
make
を実行すると、境界条件だけが異なる2種類のプログラムが生成されます。このように、周期境界条件のもとでは境界を飛び越えて反対側の値にアクセスできます。
$ ./main-cyclic.out index: 0 1 2 3 4 5 6 7 value: 0 1 2 3 4 5 6 7 index: 0 1 2 3 4 5 6 7 value: 1 2 3 4 5 6 7 8 index: 0 1 2 3 4 5 6 7 value: 80102 10203 20304 30405 40506 50607 60708 70801 total: 363636
一方、自由境界のもとでは袖領域が追加され、0未満、あるいはlocalSize
以上の添字の範囲にもアクセスできます。
$ ./main-open.out index: -1 0 1 2 3 4 5 6 7 8 value: -1 0 1 2 3 4 5 6 7 8 index: -1 0 1 2 3 4 5 6 7 8 value: 0 1 2 3 4 5 6 7 8 9 index: -1 0 1 2 3 4 5 6 7 8 value: 0 102 10203 20304 30405 40506 50607 60708 70809 0 total: 283644