【问题标题】:What functions are cached in Haskell?Haskell 中缓存了哪些函数?
【发布时间】:2019-04-01 11:59:03
【问题描述】:

我有以下代码:

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 并没有解决我的问题。

【问题讨论】:

  • 简短的版本是Constant Applicative Forms (CAFs) 被记忆。 (留下这个作为评论,因为它不是一个完整的答案。)
  • 如果 30 需要 3 秒,则根本不记忆。即您确实构建了结果缓存,但函数本身不使用该缓存。有关记忆斐波那契的示例,请参见例如this.

标签: haskell caching memoization


【解决方案1】:

基本上,您定义的函数的行为如下:

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 可以执行的优化,但不能执行,因为它很容易使性能显着变差。

【讨论】:

  • 这个答案和 Jon Purdy 的评论解决了我的问题。谢谢!
猜你喜欢
  • 1970-01-01
  • 2014-05-01
  • 2011-01-14
  • 2017-02-06
  • 1970-01-01
  • 2014-03-08
  • 2010-09-23
  • 2021-08-25
  • 1970-01-01
相关资源
最近更新 更多