[英]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.