【问题标题】:memoization question - My memo dict performs better than lru_cache. Why?memoization question - 我的 memo dict 比 lru_cache 表现更好。为什么?
【发布时间】:2021-12-29 16:05:43
【问题描述】:

我一直在玩记忆和lru_cache...我有一个简短的问题,为什么我的记忆代码比lru_cache 运行得更好。

我的代码:

memo = {}
def fib(n): 
    if n == 0:
        return 0
    elif n < 2: 
        return 1
    else:
        if n not in memo:
            memo[n] = fib(n-1) + fib(n-2)
    return memo[n]
print(fib(900))

lru_cache 代码:

from functools import lru_cache


@lru_cache(maxsize=1000)
def fib(n):
    if n == 0:
        return 0
    elif n < 2:
        return 1
    else:
       return fib(n -1) + fib(n -2)
    
    
print(fib(499))

第二次尝试查找第 500 个 fib 编号时,我在 lru_cache 中收到以下错误:

lk/Documents/VSCode/testing_zone/fibonacci_examples/fib_with_lru_cache.py
Traceback (most recent call last):
  File "c:\Users\lk\Documents\VSCode\testing_zone\fibonacci_examples\fib_with_lru_cache.py", line 14, in <module>
    print(fib(500))
  File "c:\Users\lk\Documents\VSCode\testing_zone\fibonacci_examples\fib_with_lru_cache.py", line 11, in fib
    return fib(n -1) + fib(n -2)
  File "c:\Users\lk\Documents\VSCode\testing_zone\fibonacci_examples\fib_with_lru_cache.py", line 11, in fib
    return fib(n -1) + fib(n -2)
  File "c:\Users\lk\Documents\VSCode\testing_zone\fibonacci_examples\fib_with_lru_cache.py", line 11, in fib
    return fib(n -1) + fib(n -2)
  [Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded 

然而,在我简单的 dict memoization 中,我可以在遇到同样的错误之前达到 900 个

lk/Documents/VSCode/testing_zone/fibonacci_examples/fib_with_memo.py
54877108839480000051413673948383714443800519309123592724494953427039811201064341234954387521525390615504949092187441218246679104731442473022013980160407007017175697317900483275246652938800

我的理解是 dict 和 lru_cache 的操作方式相同,它们引用 dict 或 lru_cache 在调用 fib(n) 函数之前查看 n 是否在其中。

我的问题是为什么他们的表现不一样?我是否错误配置了代码,或者是否需要与lru_cache 一起使用其他优化/代码?

lru_cache 必须正常工作,因为fib(400) 几乎是立即返回的,如果没有装饰器,则不会发生这种情况。

【问题讨论】:

  • 能否提供计时状态?为什么你认为一个比另一个更好>
  • lru_cache 名称中可能有提示 - 是的,它使用 dict 进行缓存,但它还会记录最近的使用情况,并在缓存已满后驱逐最少使用的条目。与不执行 lru 位的 functools.cache 进行比较。
  • @sahasrara62,我不认为一个比另一个更好。我的问题更多是关于为什么我可以使用 dict 方法计算几乎两倍的 fib(n) 900,而在遇到 RecursionError 之前使用 lru_cache 方法只能计算 499。引擎盖下一定发生了一些事情,我不知道,因为我的理解是它们的运作方式相同。干杯,利亚姆
  • @balmy Nah,即使使用@lru_cache(maxsize=3),它在 n=500 左右也能正常工作。
  • @lru_cache 装饰器将您的函数替换为检查缓存的包装函数,然后可能调用原始函数 - 因此堆栈上有 两个 函数调用fib() 的每次实际调用。您的版本将缓存检查和实际操作合并到一个函数中,因此它消耗堆栈级别的速度减半。

标签: python python-3.x memoization


【解决方案1】:

您的确切问题是@lru_cache 引入了第二个函数调用。它实际上对函数 fib 进行了包装。

当您认为自己在调用 fib 时,实际上是在调用包装程序。它查看该值是否在其缓存中。如果是,则返回该值;如果不是,则调用保存的原始 fib 定义,将返回的值放入其缓存中,然后返回该值。

所以当你调用fib(500)时,你的函数调用深度是1000。你的记忆程序已经将它的缓存内联到函数体中,所以调用深度只有500。因此最大递归的错误。

您可以使用sys.getrecursionlimit() 找出当前的递归限制。您可以使用sys.setrecursionlimit() 修改限制。

【讨论】:

  • 真正有用的解释! - 谢谢。绝对是我从未考虑过的关于堆栈本身的装饰器调用的事情!
  • 我还要补充一点,我一直使用@lru_cache(或@cache,如果我使用≥3.9)。添加一行代码的简单性远远超过任何问题。编写自己的备忘录并确保涵盖每个案例既乏味又容易出错。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-02-13
  • 1970-01-01
  • 1970-01-01
  • 2017-12-30
  • 2020-04-27
  • 1970-01-01
  • 2018-06-14
相关资源
最近更新 更多