我有以下代码:
memoize f = (map f [0 ..] !!)
fib' 0 = 1
fib' 1 = 1
fib' n = fib' (n - 1) + fib' (n - 2)
fibMemo n = memoize fib' n
fibMemo' = memoize fib'
(我知道斐波那契实现具有指数的时间复杂度,并且不使用缓存)
我第一次执行fibmemo' 30
花费3秒,第二次花费〜0秒,因为结果被缓存了。但是第一个版本fibmemo
不会缓存结果,它总是需要3秒钟才能执行。唯一的区别是定义(据我所知是等效的。)
所以我的问题是,Haskell中缓存了哪些函数?
我已经读过https://wiki.haskell.org/Memoization,但无法解决我的问题。
答案 0 :(得分:4)
本质上,您定义的功能的行为如下:
fibMemo n = let m = map fib' [0..] in m !! n
fibMemo' = let m = map fib' [0..] in (m !!)
为什么fibMmemo'
更有效率?好吧,我们可以将其重写为
fibMemo' = let m = map fib' [0..] in \n -> m !! n
,这更清楚地说明了在将m
用作输入之前创建了单个列表n
。这意味着对fibMemo'
的所有调用都将使用相同的m
。第一个调用缓慢评估m
的一部分,随后的调用将重用该缓存的结果(当然,假设该调用命中了缓存,否则将评估和缓存m
的另一部分)。 / p>
相反,fibMemo
等同于
fibMemo = \n -> let m = map fib' [0..] in m !! n
,它在创建列表n
之前接受输入m
。因此,每个调用都会获得一个新的缓存,这毫无意义,因为缓存的全部目的是稍后再使用。
在性能方面,lambda \n ->
与let m = ..
的顺序非常重要。由于m = ..
不使用n
,因此从技术上讲let m = ..
可以向外浮动,从本质上将fibMemo
变成fibMemo'
,而不会影响语义。但是,正如您发现的那样,这通常不会保留性能!
这确实是GHC可以执行的优化,但不能实现,因为它很容易使性能明显变差。