Python中的递归,memoization和可变默认参数

“Base”意思是不使用lru_cache.所有这些都“足够快” – 我不是在寻找最快的算法 – 但时间让我感到惊讶,所以我希望我能学到一些关于
Python“工作原理”的东西.

简单循环(/尾递归):

def fibonacci(n):
    a, b = 0, 1
    if n in (a, b): return n
    for _ in range(n - 1):
        a, b = b, a + b
    return b

简单记忆:

def fibonacci(n, memo={0:0, 1:1}):
    if len(memo) <= n:
        memo[n] = fibonacci(n - 1) + fibonacci(n - 2)
    return memo[n]

使用发电机:

def fib_seq():
    a, b = 0, 1
    yield a
    yield b
    while True:
        a, b = b, a + b
        yield b

def fibonacci(n):
    return next(x for (i, x) in enumerate(fib_seq()) if i == n)

我期望第一个,简单易行,是最快的.不是.尽管有递归和大量函数调用,但第二个是迄今为止最快的.第三个很酷,并使用“现代”功能,但更慢,这是令人失望的. (我很想把发电机想象成某种方式来替代记忆 – 因为它们记住了它们的状态 – 而且由于它们是用C实现的,我希望它们能更快.)

典型结果:

loop: about 140 μs
memo: about 430 ns
genr: about 250 μs

那么,任何人都能解释一下,为什么memoization比简单循环快一个数量级?

编辑:

现在清楚,我(像我之前的很多人)只是偶然发现了Python的可变默认参数.此行为解释了执行速度的实际和明显增益.

最佳答案 你所看到的是记忆的全部要点.第一次调用该函数时,备忘录缓存为空,必须递归.但是下次使用相同或更低的参数调用它时,答案已经在缓存中,因此它会立即返回.如果您执行数千个呼叫,则您将所有其他呼叫的第一个呼叫时间分摊.这就是使memoization成为如此有用的优化的原因,你只需要第一次支付成本.

如果要查看缓存是新鲜的并且必须执行所有递归所需的时间,可以将初始缓存作为基准调用中的显式参数传递:

fibonacci(100, {0:0, 1:1})
点赞