【问题标题】:Python recursive generators performancePython递归生成器性能
【发布时间】:2013-05-19 20:29:29
【问题描述】:

在 python 中,将纯递归函数更改为递归生成器(不是普通生成器)时,性能似乎会下降。

例如,下面是两个查找列表所有组合的函数之间的性能比较:

from datetime import datetime as dt

def rec_subsets(ms, i=0, s=[]):
    if i == len(ms):
        # do something with s
        return
    rec_subsets(ms, i+1, s)
    rec_subsets(ms, i+1, s + [ms[i]])

def gen_subsets(ms, i=0, s=[]):
    if i == len(ms):
        yield s
        return
    for a in gen_subsets(ms, i+1, s): yield a
    for a in gen_subsets(ms, i+1, s + [ms[i]]): yield a

t1 = dt.now()
rec_subsets(range(20))
t2 = dt.now()
print t2 - t1

t1 = dt.now()
for _ in gen_subsets(range(20)): pass
t2 = dt.now()
print t2 - t1

输出如下:

0:00:01.027000  # rec_subsets
0:00:02.860000  # gen_subsets

人们自然会期望 gen_subsets 大约与 rec_subsets 一样快,但事实并非如此,它要慢得多。

这是正常的还是我错过了什么?

【问题讨论】:

  • 您需要在# do something with s 处放置一些代码,然后才能进行有意义的计时。
  • 没必要,gen_subsets 同样什么也不做。我在这两种情况下都做了类似的事情,以防万一(添加到一个空的全局列表)得到相同的结果。
  • 但是为什么你会期望添加 yield 语句可以让代码更快呢?
  • 嗯,如果这是一个有效/有根据的假设,这就是我想通过首先提出这个问题来了解的。与纯递归相比,递归生成器非常好用且用途广泛。如果他们的表现也不错,那就太好了。
  • 顺便说一句,最初的问题是关于性能的,编辑不符合这个目的。

标签: python performance recursion generator


【解决方案1】:

rec_subsets() 仍然更快(对于range(20)),即使在# do something with s 的位置添加result.append(s) 并且gen_subsets()rec_subsets() 的结果都已被消耗。

PEP 380 (yield from syntax support) 的以下引用可能会解释这一点:

使用专门的语法开辟了优化的可能性 当有很长的发电机链时。这样的链条可能会出现,因为 例如,当递归遍历树结构时。开销 传递__next__() 调用并在链上向上传递值 在最坏的情况下,可能会导致应该是 O(n) 的操作变成 情况下,O(n**2)

你可以使用itertools.combinations()生成一个powerset:

from itertools import combinations

def subsets_comb(lst):
    return (comb for r in range(len(lst)+1) for comb in combinations(lst, r))

range(20) 在我的机器上更快:

name                    time ratio comment
subsets_comb        227 msec  1.00 [range(0, 20)]
subsets_ipowerset   476 msec  2.10 [range(0, 20)]
subsets_rec         957 msec  4.22 [range(0, 20)]
subsets_gen_pep380 2.34  sec 10.29 [range(0, 20)]
subsets_gen        2.63  sec 11.59 [range(0, 20)]

要重现结果,run time-subsets.py

【讨论】:

  • 我认为这里的教训是用非递归生成器代替递归函数来代替生产代码。
猜你喜欢
  • 2021-05-22
  • 2016-05-03
  • 2013-09-16
  • 2012-05-03
  • 2010-10-25
  • 2021-10-17
  • 2020-11-04
  • 1970-01-01
  • 2017-11-18
相关资源
最近更新 更多