【问题标题】:Non-pointfree style is substantially slower非无点风格要慢得多
【发布时间】:2016-01-01 09:17:34
【问题描述】:

我有以下经常被引用的代码来计算 Haskell 中的第 n 个斐波那契数:

fibonacci :: Int -> Integer
fibonacci = (map fib [0..] !!)
    where fib 0 = 0
          fib 1 = 1
          fib n = fibonacci (n-2) + fibonacci (n-1)  

使用它,我可以进行如下调用:

ghci> fibonacci 1000

并获得几乎即时的答复。

但是,如果我修改上面的代码使其不是无点样式,即

fibonacci :: Int -> Integer
fibonacci x = (map fib [0..] !!) x
    where fib 0 = 0
          fib 1 = 1
          fib n = fibonacci (n-2) + fibonacci (n-1) 

它的速度要慢得多。在某种程度上,诸如

之类的调用
ghci> fibonacci 1000

挂起。

我的理解是上面两段代码是等价的,但 GHCi 不一样。有人对此行为有解释吗?

【问题讨论】:

  • 第一个定义更像fibonacci = let k = map fib [0 ..] in \x -> k !! x。它可能会共享结果列表,而不是每次都重新计算。
  • 嗯,所以我很满意正是这种“分享”(记忆)让第一个超级快。但是为什么要对第二个做同样的事情呢?
  • 你在 GHCI 中运行你的代码,没有优化。尝试使用 -O2 编译这两个函数,看看 GHC 是否足够聪明,可以为您解决问题。
  • 更新:使用优化编译并查看生成的核心显示它是相同。为了将来参考,如果您对性能感兴趣,您必须进行优化编译!
  • @user2407038,无论如何,我认为未优化代码差异的原因很有趣。

标签: haskell memoization


【解决方案1】:

要观察差异,您可能应该查看 Core。我猜这归结为(大致)比较

let f = map fib [0..] in \x -> f !! x

\x -> let f = map fib [0..] in f !! x

后者将在每次调用时从头开始重新计算f。前者没有,有效地为每次调用缓存相同的f

碰巧在这种特定情况下,一旦启用优化,GHC 就能够将第二个优化为第一个。

但是请注意,GHC 并不总是执行这种转换,因为这并不总是一种优化。第一个使用的缓存永远保存在内存中。这可能会导致内存浪费,具体取决于手头的功能。

【讨论】:

    【解决方案2】:

    我试图找到它但被击中了。我想我家里的电脑上有它。 我读到的是使用定点的函数本质上更快。 使用定点还有其他原因。我在编写这个迭代斐波那契函数时遇到了一个。我想看看迭代版本的性能如何,然后我意识到我没有现成的测量方法。我是 Haskell 新手。但这里有一个迭代版本供某人测试。 除非我在第一个最后一个函数之后使用点,否则我无法定义它。 我无法进一步减少它。 [0,1] 参数是固定的,不作为参数值提供。

    Prelude> fib = last . flip take (iterate (\ls -> ls ++ [last ls + last (init ls)]) [0,1])
    Prelude> fib 25
    

    [0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多