我想实现以下天真(一阶)有限差分函数:
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
相关的任何优化,因此我们确信“聪明”的编译器不会优化这两行。
我不知道Haskell还不知道如何解决这个问题。我害怕
let temp = x + h
hh = temp - x
in ((f $ x + hh) - (f x)) / h
将被Haskell(或它使用的后端)优化掉。如何在此处获得volatile
的等效值(如果可能的话,不会牺牲懒惰)?我不介意GHC的具体答案。
答案 0 :(得分:6)
我有两个解决方案和建议:
第一个解决方案:您可以保证使用两个辅助函数和NOINLINE编译指示不会优化它:
norm1 x h = x+h
{-# NOINLINE norm1 #-}
norm2 x tmp = tmp-x
{-# NOINLINE norm2 #-}
normh x h = norm2 x (norm1 x h)
这样可行,但会带来一小笔费用。
第二种解决方案:使用volatile在C中写入规范化函数,并通过FFI调用它。性能损失很小。
现在提出建议:目前数学没有优化,所以它目前可以正常工作。你担心它会在未来的编译器中崩溃。我认为这不太可能,但也不是不太可能我也不想防范它。因此,请编写一些涵盖相关案例的单元测试。然后如果它确实在将来中断(出于任何原因),你就会确切地知道原因。
答案 1 :(得分:2)
一种方法是看核心。
专注于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
对于多态版本同样(甚至更少的重写)。
因此,虽然内联变量,但数学并未优化。 除了看核心,我想不出一种保证你想要的财产的方法。
答案 2 :(得分:1)
我不认为
temp = unsafePerformIO $ return $ x + h
会得到优化。只是一个猜测。