“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})