“基本”的含义不只是使用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
那么任何人都可以特别说明为什么记忆比简单循环快一个数量级吗?
编辑:
现在知道我(像我之前的许多人一样)只是偶然发现了Python的可变默认参数。此行为说明了执行速度的实际和表面上的收益。
答案 0 :(得分:1)
您所看到的是记忆的重点。第一次调用该函数时,memo
缓存为空,因此必须递归。但是,下次使用相同或较低的参数调用它时,答案已经在缓存中,因此它会立即返回。如果您执行了数千个通话,那么您将摊销第一个通话的时间。这就是使备忘成为如此有用的优化的原因,而您只需第一次支付费用。
如果要查看刷新缓存需要多长时间,并且必须执行所有递归操作,则可以将初始缓存作为基准调用中的显式参数传递:
fibonacci(100, {0:0, 1:1})