3

次の素朴な (一次) 有限差分関数を実装したいと思います。

finite_difference :: Fractional a => a -> (a -> a) -> a -> a
finite_difference h f x = ((f $ x + h) - (f x)) / h

ご存知かもしれませんが、微妙な問題があります。 とが正確に表現可能な数だけ異なることを確認する必要が(x + h)あります。xそれ以外の場合、壊滅的なキャンセルを伴うという事実に影響されて、結果に大きなエラーが発生します(f $ x + h) - (f x)(慎重に選択する必要がありますhが、それは私の問題ではありません)。

C または C++ では、この問題は次のように解決できます。

volatile double temp = x + h;
h = temp - x;

volatile修飾子は変数に関連する最適化を無効にするためtemp、「賢い」コンパイラはこれらの 2 行を最適化しないことが保証されています。

これを解決する方法を知るには、まだ十分な Haskell を知りません。私はそれを恐れています

let temp = x + h
    hh = temp - x 
in ((f $ x + hh) - (f x)) / h

Haskell (または Haskell が使用するバックエンド) によって最適化されます。ここに相当するものを取得するにはどうすればよいですvolatileか (怠惰を犠牲にすることなく可能であれば)? GHC固有の回答は気にしません。

4

3 に答える 3

6

2 つの解決策と提案があります。

最初の解決策: 2 つのヘルパー関数と NOINLINE プラグマを使用すると、これが最適化されないことを保証できます。

norm1 x h = x+h
{-# NOINLINE norm1 #-}

norm2 x tmp = tmp-x
{-# NOINLINE norm2 #-}

normh x h = norm2 x (norm1 x h)

これは機能しますが、わずかなコストがかかります。

2 番目の解決策: volatile を使用して C で正規化関数を記述し、FFI を介して呼び出します。パフォーマンスの低下は最小限に抑えられます。

提案のために:現在、数学は最適化されていないため、現時点では適切に機能します。将来のコンパイラで壊れるのではないかと心配しています。これはありそうもないと思いますが、私もそれを防ぎたくないほどありそうもないことではありません。そのため、問題のケースをカバーする単体テストをいくつか作成してください。その後、将来 (何らかの理由で) 壊れた場合、その理由が正確にわかります。

于 2011-04-12T18:45:24.580 に答える
2

1つの方法は、コアを確認することです。

に特化するDoubles(これは、最適化をトリガーする可能性が最も高いケースです):

finite_difference :: Double -> (Double -> Double) -> Double -> Double
finite_difference h f x = ((f $ x + hh) - (f x)) / h
   where
        temp = x + h
        hh   = temp - x 

コンパイルされます:

A.$wfinite_difference h f x =
    case f (case x of
                  D# x' -> D# (+## x' (-## (+## x' h) x'))
           ) of 
        D# x'' -> case f x of D# y -> /## (-## x'' y) h

ポリモーフィックバージョンについても同様です(書き換えはさらに少なくなります)。

したがって、変数はインライン化されていますが、計算は最適化されていません。コアを見る以外に、私はあなたが望む特性を保証する方法を考えることができません。

于 2011-04-12T18:23:35.897 に答える
1

そうは思わない

temp = unsafePerformIO $ return $ x + h

最適化されます。推測です。

于 2011-04-12T18:20:06.210 に答える