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

发布于 2024-10-31 20:24:51 字数 753 浏览 2 评论 0原文

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

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) - (fx) 涉及灾难性取消(并且必须谨慎选择 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(或其使用的后端)会对其进行优化。我如何在这里获得 挥发性 的等价物(如果可能而不牺牲懒惰)?我不介意 GHC 的具体答案。

I'd like to implement the following naive (first order) finite differencing function:

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

As you may know, there is a subtle problem: one has to make sure that (x + h) and x differ by an exactly representable number. Otherwise, the result has a huge error, leveraged by the fact that (f $ x + h) - (f x) involves catastrophic cancellation (and one has to carefully choose h, but that is not my problem here).

In C or C++, the problem can be solved like this:

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

and the volatile modifier disables any optimization pertaining to the variable temp, so we are assured that a "clever" compiler will not optimize away those two lines.

I don't know enough Haskell yet to know how to solve this. I'm afraid that

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

will get optimized away by Haskell (or the backend it uses). How do I get the equivalent of volatile here (if possible without sacrificing laziness)? I don't mind GHC specific answers.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

哆啦不做梦 2024-11-07 20:24:51

我有两个解决方案和一个建议:

第一个解决方案:您可以保证这不会通过两个辅助函数和 NOINLINE pragma 进行优化:

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 调用它。性能损失将是最小的。

现在提出建议:目前数学尚未优化,因此目前可以正常工作。你担心它会在未来的编译器中崩溃。我认为这不太可能,但也不是不太可能,以至于我不想防范它。因此,编写一些涵盖相关案例的单元测试。然后,如果将来它确实损坏了(出于任何原因),您就会确切地知道原因。

I have two solutions and a suggestion:

First solution: You can guarantee that this won't be optimized out with two helper functions and the NOINLINE pragma:

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

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

normh x h = norm2 x (norm1 x h)

This will work, but will introduce a small cost.

Second solution: Write the normalization function in C using volatile and call it through the FFI. The performance penalty will be minimal.

Now for the suggestion: Currently the math isn't optimized out, so it will work properly at present. You're afraid it will break in a future compiler. I think this is unlikely, but not so unlikely that I wouldn't want to guard against it also. So write some unit tests that cover the cases in question. Then if it does break in the future (for any reason), you'll know exactly why.

胡渣熟男 2024-11-07 20:24:51

一种方法是查看核心。

专门针对双精度(这将是最有可能触发某些优化的情况):

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

对于多态版本也类似(甚至更少的重写)。

因此,虽然变量是内联的,但数学并没有被优化掉。
除了查看核心之外,我想不出一种方法来保证您想要的属性。

One way is to look at the Core.

Specializing to Doubles (which will be the case most likely to trigger some optimization):

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 

Is compiled to:

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

And similarly (with even less rewriting) for the polymorphic version.

So while the variables are inlined, the math isn't optimized away.
Beyond looking at the Core, I can't think of a way to guarantee the property you want.

离不开的别离 2024-11-07 20:24:51

我认为这不会

temp = unsafePerformIO $ return $ x + h

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

I don't think that

temp = unsafePerformIO $ return $ x + h

would get optimised out. Just a guess.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文