HaskellでGPUプログラミングしてみた
repa触ってみたらGPUも触ってみたくなったので触りました。
ライフゲームを略。
次の時刻の盤面を計算する部分です。ドキュメントやサンプルを見ると、どうやらstencilを使うと良いようです。
step :: Board -> Acc Board step arr = Acc.stencil stencil2D (Constant 0) . Acc.use $ arr
cellとその周囲cellから次の時刻の値を計算出来るという、流体シミュレーションのために有るような関数ですね。
その際、境界条件をどうするかが第二引数です。
data Boundary a = Clamp | Mirror | Wrap | Constant a
最外値がその外もそのまま続いているかのように扱うのがClamp、範囲外の値が反転しているものがMirror、境界外が反対側と繋がっているものがWrap、定数値が広がっているように扱うのがConstant aです。
ライフゲームでは範囲外の値は死んでいると判断されれば十分なのでConstant 0にしています。
そしてlifecheck部分。
stencil2D :: Stencil3x3 Int -> Exp Int stencil2D ((a,b,c), (d,e,f), (g,h,i)) = (cnt ==* 2) ? (e, (cnt ==* 3) ? (1, 0)) where cnt = a+b+c+d+f+g+h+i
初め普通にガードで書いたのですが、Eq.==は使えません的な実行時エラー出ました。どうやらGPU上の表現であるらしいAcc, Expなどといった値はいつもの関数をそのまま使う事は出来ないようです。
ここでは(?)演算子を使って分岐を行っています。
(?) :: Elt t => Exp Bool -> (Exp t, Exp t) -> Exp t
丁度三項演算子の様なものですね。結果はtupleでまとまっているので2項演算子ですが。
比較演算子も==, <, >といったものではなくて、==*, /=*, <*, >*, <=*, >=*といった*付きの演算子が用意されています。
GPU上でのAccな値を計算する関数がrunです。
repeatStep :: BoardSize -> Board -> IO () repeatStep siz board = do let board' = CUDA.run $ step board threadDelay 120000 clearDisp printRect siz board' repeatStep siz board'
一ステップ毎にGPU上で計算しているとあまり効果がないような...
だけど多分Accな値は触りようが無い気がするので今回は仕方ないような。高fpsが要求される場面ではGPUとの連携はどうするのが良いのでしょうね。
ソースコードです。
そういえばcabal install accelerate-cudaで少しつまづいたのでした。
$ export DYLD_LIBRARY_PATH=/usr/local/cuda/lib
などとしたら通る様になりましたが。