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 b
GHC がそれらを削除すると、次のようになります。
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
この場合に使用できるトリックはありますか?