Haskell 中缓存了哪些函数?
What functions are cached in Haskell?
我有以下代码:
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 但没有解决我的问题。
本质上,您定义的函数的行为如下:
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
这使得在 n
作为输入之前创建单个列表 m
变得更加清楚。这意味着对 fibMemo'
的所有调用都将使用相同的 m
。第一次调用缓慢地评估 m
的一部分,后续调用将重用缓存的结果(当然,假设调用命中缓存,否则 m
的另一部分被评估和缓存)。
相反,fibMemo
等同于
fibMemo = \n -> let m = map fib' [0..] in m !! n
在创建列表 m
之前接受输入 n
。所以,每次调用都会得到一个新的缓存,这是没有意义的,因为缓存的全部目的是稍后重用它。
lambda \n ->
与 let m = ..
的顺序在性能方面非常重要。由于 m = ..
不使用 n
,因此从技术上讲 let m = ..
可以向外浮动,实质上将 fibMemo
变成 fibMemo'
,而不影响语义。但是,正如您所发现的,这通常不会保持性能!
这确实是 GHC 可以执行但没有执行的优化,因为它很容易使性能显着变差。
我有以下代码:
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 但没有解决我的问题。
本质上,您定义的函数的行为如下:
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
这使得在 n
作为输入之前创建单个列表 m
变得更加清楚。这意味着对 fibMemo'
的所有调用都将使用相同的 m
。第一次调用缓慢地评估 m
的一部分,后续调用将重用缓存的结果(当然,假设调用命中缓存,否则 m
的另一部分被评估和缓存)。
相反,fibMemo
等同于
fibMemo = \n -> let m = map fib' [0..] in m !! n
在创建列表 m
之前接受输入 n
。所以,每次调用都会得到一个新的缓存,这是没有意义的,因为缓存的全部目的是稍后重用它。
lambda \n ->
与 let m = ..
的顺序在性能方面非常重要。由于 m = ..
不使用 n
,因此从技术上讲 let m = ..
可以向外浮动,实质上将 fibMemo
变成 fibMemo'
,而不影响语义。但是,正如您所发现的,这通常不会保持性能!
这确实是 GHC 可以执行但没有执行的优化,因为它很容易使性能显着变差。