为啥python斐波那契序列循环比递归慢?
Posted
技术标签:
【中文标题】为啥python斐波那契序列循环比递归慢?【英文标题】:Why python fibonacci sequence loop is slower than recursion?为什么python斐波那契序列循环比递归慢? 【发布时间】:2021-04-18 04:46:44 【问题描述】:下面是著名的斐波那契数列示例
# test.py
import sys
sys.setrecursionlimit(20000)
def fib_loop(n):
if n <= 1:
return n
fn, fnm1 = 1, 0
for _ in range(2, n+1):
fn, fnm1 = fn + fnm1, fn
return fn
def fib_recursion(n, memo=):
if n <= 1:
return n
if n not in memo:
memo[n] = fib_recursion(n-1, memo) + fib_recursion(n-2, memo)
return memo[n]
和大家一样,我曾经认为循环变体会比递归变体快得多。然而,实际的结果却是相当惊人的。
$ python3 -m timeit "import test; test.fib_loop(10000)"
100 loops, best of 5: 1.93 msec per loop
$ python3 -m timeit "import test; test.fib_recursion(10000)"
500000 loops, best of 5: 471 nsec per loop
我不知道为什么。有人能帮帮我吗?
【问题讨论】:
删除memo
然后检查差异...
如果您不提供任何数字来计时,代码将在number=1000000
执行 (see here) 上取平均值。您记住结果,因此对于 999999 尝试,它只是对(一次)生成的 dict 的 O(1) 查找,而循环必须重新计算数字 1000000 次。
***.com/q/1132941/3929826
【参考方案1】:
因为你正在记忆你的结果。而且您在每次迭代中都重复使用该备忘录字典。所以第一次运行很慢。在其他所有调用中,它都是一个简单的 dict-lookup。
如果你使用number=1
,所以它只运行一次,你会发现第一次调用实际上更慢
>>> import sys
>>> sys.setrecursionlimit(20000)
>>>
>>> def fib_loop(n):
... if n <= 1:
... return n
... fn, fnm1 = 1, 0
... for _ in range(2, n+1):
... fn, fnm1 = fn + fnm1, fn
... return fn
...
>>> def fib_recursion(n, memo=):
... if n <= 1:
... return n
... if n not in memo:
... memo[n] = fib_recursion(n-1, memo) + fib_recursion(n-2, memo)
... return memo[n]
...
>>> import timeit
>>> timeit.timeit("fib_loop(1000)", setup="from __main__ import fib_loop", number=1)
9.027599999456015e-05
>>> timeit.timeit("fib_recursion(1000)", setup="from __main__ import fib_recursion", number=1)
0.0016194200000114733
或者,如果你为每个外部调用传递一个新的 memo dict,你会得到相同的行为:
>>> timeit.timeit("fib_recursion(1000, )", setup="from __main__ import fib_recursion", number=1000)
0.38679519899999093
>>> timeit.timeit("fib_loop(1000)", setup="from __main__ import fib_loop", number=1000)
0.07079556799999409
【讨论】:
我不知道timeit函数又重用了memo
变量。不是局部变量吗?
@GulimProject 变量的范围无关紧要,它每次都在重用同一个对象。无论如何,这通常是记忆化的工作方式,否则它会如何工作?
哇,这真是令人惊讶。不知道默认值每次都是共享和重用的……
@GulimProject 请参阅why-are-default-arguments-evaluated-at-definition-time - dict 在函数定义中创建一次,并一直存在,直到您使用 python 重新启动文件。提供带有 list 或 dict "defaults" 的函数时,这是一个常见错误,因此您通常提供 None 作为默认值,如果您不想要这种行为,则在函数内部执行“defaultlist = defaultlist or []”(除非您想要它,那么你可以按原样使用它)
@juanpa.arrivillaga 不,即使没有它也会起作用,因为我在递归调用中将memo
作为参数传递。有趣的是,即使我不通过它,该函数仍然记住结果。以上是关于为啥python斐波那契序列循环比递归慢?的主要内容,如果未能解决你的问题,请参考以下文章