繁体   English   中英

GHC优化

[英]GHC optimization

下面的两个Haskell函数似乎不同,索引变量是隐式还是显式,但性能差异是两个数量级。

此函数大约需要0.03秒来计算mfib 30:

let mfib = (map fib [0..] !!)
  where
    fib 0 = 0
    fib 1 = 1
    fib x = mfib (x-1) + mfib (x-2)

对于mfib 30,此功能大约需要3秒钟:

let mfib i = map fib [0..] !! i
  where
    fib 0 = 0
    fib 1 = 1
    fib x = mfib (x-1) + mfib (x-2)

我猜它与GHC内联规则有关,并且一直在尝试添加内联/无内置编译指示以获得匹配的性能。

编辑:我理解如何查找惰性列表可以用来记忆fib函数以及为什么传统的fib定义非常慢。 我期待memoization在第二个函数以及第一个函数中工作,并且不理解为什么它不是。

在查看desugared代码时,更容易理解这些差异,因此这里有两个函数的部分脱落版本。

let mfib = let fib 0 = 0
               fib 1 = 1
               fib x = mfib (x-1) + mfib (x-2)
           in (!!) (map fib [0..])

let mfib = \i ->
               let fib 0 = 0
                   fib 1 = 1
                   fib x = mfib (x-1) + mfib (x-2)
               in map fib [0..] !! i

请注意,在第二个程序中,表达式map fib [0..]出现在\\i -> ... ,因此它(通常,没有优化)将针对i每个值进行评估。 请参阅GHC Haskell中何时自动记忆?

不,这与内联无关。 区别在于mfib = (map fib [0..] !!)没有参数。 它当然仍然是一个函数,但是预先评估该函数不需要传递任何参数。 特别是,评估此mfib将以一种可以为所有索引重用的方式生成fib列表。

OTOH, mfib i = map fib [0..] !! i mfib i = map fib [0..] !! i意思是当你实际传递一个参数i时,只会考虑整个where块。

如果您反复多次评估函数,则两者仅相同。 不幸的是,对于第二个版本,函数自己的递归已经一次又一次地调用它! 所以mfib (x-1) + mfib (x-2)然后需要完成mfib (x-1)的整个工作, 然后再完成mfib (x-2)的全部工作。 所以mfib n花费两倍以上的计算成本mfib (n-1)因此mfib ∈O(2 N)。

这是非常浪费的,因为mfib (x-2)中的大部分术语也已经在mfib (x-1)并且可以简单地重复使用。 嗯,这正是你的第一个版本所做的,因为它计算一次和所有索引的fib列表,所以评估mfib (x-1)已经完成了大部分工作,然后可以简单地由mfib (x-2)重新读取mfib (x-2) ,将复杂度降低到多项式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM