Haskell的有限差异,或如何禁用潜在的优化

时间:2011-04-12 18:01:37

标签: optimization haskell numerical-methods rounding-error

我想实现以下天真(一阶)有限差分函数:

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的具体答案。

3 个答案:

答案 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

会得到优化。只是一个猜测。