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

问题描述

|| 我想实现以下朴素(一阶)有限差分函数
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(或它使用的后端)将对其进行优化。在这里,我如何得到等价的6英镑(如果可能的话,不牺牲懒惰)?我不介意GHC的具体答案。     

解决方法

我有两个解决方案和一个建议: 第一个解决方案:您可以确保不会通过两个辅助函数和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对其进行调用。性能损失将是最小的。 现在提出建议:当前数学尚未优化,因此目前可以正常使用。您担心它会在将来的编译器中损坏。我认为这不太可能,但也不太可能使我也不想对此加以防范。因此,编写一些涵盖相关案例的单元测试。然后,如果将来确实发生故障(出于任何原因),您将确切知道原因。     ,一种方法是查看核心。 专攻
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
并且类似地(甚至更少的重写)用于多态版本。 因此,当内联变量时,并没有优化数学。 除了看核心之外,我想不出一种保证所需属性的方法。     ,我不认为
temp = unsafePerformIO $ return $ x + h
将得到优化。只是一个猜测。