GHC での低レベルの手動ループの最適化に苦労しています。私のプログラムには、数値計算を実行するいくつかのループが含まれています。実際のデータは他のデータ構造にラップされ、プログラムは「ループ制御フロー関数」と「計算関数」に分割され、一部のデータ構造フィールドが内部ループ内で読み取られるようになります。GHC がこれらの読み取りを内側のループの外に移動するようにします。何が起こっているかを示すために、コードを簡略化したバージョンを次に示します。
data D = D !Double !C
data C = C Double
-- This function is called in every loop iteration.
-- Parameter 'c' is loop-invariant.
exampleLoopBody i a c =
case c of C b -> a + b * fromIntegral i
-- The body of this function is a counted loop that should be optimized
foo x =
case x
of D acc0 c ->
let loop i acc =
if i > 100
then acc
else loop (i+1) (exampleLoopBody i acc c)
in loop 0 acc0
すべてのループ反復は を評価しますが、はループ不変であるcase c of C bため、冗長な計算です。c冗長な case 式をループの外に置くことで、GHC がそれを取り除くことができます。
foo x =
case x
of D acc0 c ->
case c -- This case statement inserted for optimization purposes
of C b -> b `seq` -- It will read 'b' outside of the loop
let loop i acc =
if i > 100
then acc
else loop (i+1) (exampleLoopBody i acc c)
in loop 0 acc0
コンパイラはインライン化しexampleLoopBodyます。その後、内側の case ステートメントは冗長になり、削除されます。
foo x =
case x
of D acc0 c ->
case c
of C b -> b `seq`
let loop i acc =
if i > 100
then acc
else loop (i+1) (acc + b * fromIntegral i) -- The inlined case expression disappears
in loop 0 acc0
の目的はseq、case 式がデッド コードにならないようにすることです。かどうかをseqチェックします。GHC は、が計算されたので、ループ本体でその値を再利用すると便利であることに気付きました。b_|_b
ここで問題があります。関連するすべてのデータ フィールドを厳格にしたいのです。このように、データ定義に厳密性アノテーションを挿入すると、
data C = C !Double
GHC に関する限り、seqとは何の効果もありません。case c of C bGHC がそれらを削除すると、次のようになります。
foo x =
case x
of D acc0 c ->
let loop i acc =
if i > 100
then acc
else loop (i+1) (case c of C b -> acc + b * fromIntegral i) -- Evaluate the case in every iteration
in loop 0 acc0
このコードはcase c of C b反復ごとに評価されますが、これは私が避けようとしていたことです。
に頼ることができない場合、ループ本体の外で強制的に計算するseq方法が他にわかりません。bこの場合に使用できるトリックはありますか?