はじめてのParaisoプログラミング
Paraisoは、一様メッシュ上での偏微分方程式の陽解法という分野に特化し、その分野の計算を、直胞機械(OrthotopeMachineの訳語、今決めた)と呼ばれる仮想機械のプログラムとして表します。直胞機械は、配列変数とその操作を表現する仮想機械であり、解きたい問題の内包する並列性をなるべく生かすように設計されています。たとえば、配列変数の要素を格納する順序や、アクセスする順序は自由。命令どうしの依存関係もはじめから明確で、依存性のない命令を実行する順序も、同時に実行することも自由です。Paraisoは、この直胞機械のプログラムを記述し、それを既存の言語のプログラムに翻訳し、さらに自動チューニングを行うための様々なメカニズムを備えたフレームワークです。より詳しい用語の解説などはParaisoの論文を見てください。
しかし、Paraisoにコードを生成させるためにユーザーがすべきことを、具体的に一言でいうなら、「OM
型の値を作り、コードジェネレータに渡すこと」となります。
ハローワールドの中身
前回掲載したサンプルexamples/HelloWorld/Generator.hs
には、Paraisoにコードを生成させるための全てが記されています。順にみていきましょう。
最初の十数行は#include
みたいなもんで、必要なモジュールをインクルードしています。
#!/usr/bin/env runhaskell {-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-} {-# OPTIONS -Wall #-} import Data.Tensor.TypeLevel import qualified Data.Text.IO as T import Language.Paraiso.Annotation (Annotation) import Language.Paraiso.Generator (generateIO) import qualified Language.Paraiso.Generator.Native as Native import Language.Paraiso.Name import Language.Paraiso.OM import Language.Paraiso.OM.Builder import Language.Paraiso.OM.DynValue (f2d, DynValue) import Language.Paraiso.OM.Value (StaticValue(..)) import Language.Paraiso.OM.PrettyPrint (prettyPrintA1) import Language.Paraiso.OM.Realm import qualified Language.Paraiso.OM.Reduce as Reduce import Language.Paraiso.Optimization import NumericPrelude import System.Process (system)
まずはmain
から見ていきましょう。一行目は"output"
というフォルダを作っています。二行目では、myOM
という名の直胞機械のデータフロー・グラフを出力しています。これは参考のために出力しているのであって、コード生成に必須の過程ではありません。三行目では、myOM
の定義とmySetup
というコード生成時の設定を渡してC++のコードを生成しています。
-- the main program main :: IO () main = do _ <- system "mkdir -p output" T.writeFile "output/OM.txt" $ prettyPrintA1 $ myOM _ <- generateIO mySetup myOM return ()
直胞機械を定義する時には使わないが、コード生成時にはじめて必要になる情報はLanguage.Paraiso.Generator.Native
モジュールにあるSetup
型でまとめて指定します。ここでは具体的な計算のサイズや、ライブラリを生成するフォルダを指定しています。コード生成対象の言語はデフォルトではC++です。
-- the code generation setup mySetup :: Native.Setup Vec2 Int mySetup = (Native.defaultSetup $ Vec :~ 10 :~ 20) { Native.directory = "./dist/" }
次はいよいよmyOM
の定義です。直胞機械を定義するには、名前(tableMaker
)、マシン全体の注釈、静的変数のリスト、カーネルのリストの4つをmakeOM
関数に渡します。
-- the orthotope machine to be generated myOM :: OM Vec2 Int Annotation myOM = optimize O3 $ makeOM (mkName "TableMaker") [] myVars myKernels
myOM
には、九九の表を保持するためのtable
と、その合計を計算するためのtotal
という2つの変数があります。2つの変数のRealm
は、それぞれArray
とScalar
です。Array
変数は解きたい問題の解像度とおなじサイズをもつ配列変数で、Scalar
変数は普通に1つしかない値です。それぞれの変数はName
も持っています。
-- the variables we use table :: Named (StaticValue TArray Int) table = "table" `isNameOf` StaticValue TArray undefined total :: Named (StaticValue TScalar Int) total = "total" `isNameOf` StaticValue TScalar undefined myVars :: [Named DynValue] myVars = [f2d table, f2d total]
Paraisoでは、Language.Paraiso.Name
モジュールで定義されているName
型を識別子の名前として使います。これは、ただの文字列と区別するために、Data.Text
モジュールの提供するマルチバイト文字列型Text
をnewtypeしたものです。
次にカーネルの定義です。OMのカーネルとは、一度に実行される計算のかたまりです。myOM
にはcreate
という名前のカーネルがひとつだけあります。
-- the only kernel our OM has myKernels :: [Named (Builder Vec2 Int Annotation ())] myKernels = ["create" `isNameOf` createBuilder]
カーネルを作るにはBuilder Monad
を使います。ここでは、x軸とy軸の添字を読み込んでその積を計算するカーネルを作っています。ついでに九九の表の総計も求めてみましょう。
-- the kernel builder monad createBuilder :: Builder Vec2 Int Annotation () createBuilder = do x <- bind $ loadIndex (Axis 0) y <- bind $ loadIndex (Axis 1) z <- bind $ x*y store table z store total $ reduce Reduce.Sum z