为什么生产的发电机比xrange生产的发电机更快?

时间:2016-07-28 02:48:00

标签: python python-2.7 python-3.x yield

我正在调查Python生成器并决定进行一些实验。

TOTAL = 100000000
def my_sequence():
    i = 0
    while i < TOTAL:
        yield i
        i += 1

def my_list():
    return range(TOTAL)

def my_xrange():
    return xrange(TOTAL)    

内存使用情况(使用psutil来获取进程RSS内存)和使用时间(使用time.time())如下所示:运行每个方法几次并取平均值:

sequence_of_values = my_sequence() # Memory usage: 6782976B  Time taken: 9.53674e-07 s

sequence_of_values2 = my_xrange() # Memory usage: 6774784B  Time taken: 2.14576e-06 s

list_of_values = my_list() # Memory usage: 3266207744B  Time taken: 1.80253s

我注意到使用xrange生成一个生成器与使用yield一致(略微)慢。为什么会这样?

2 个答案:

答案 0 :(得分:9)

我将在这个答案前言,说这种规模的时间可能难以准确衡量(可能最好使用timeit)并且这些优化几乎不会使你的实际程序的运行时间有什么不同......

好的,现在免责声明已经完成......

您需要注意的第一件事是,您只是为生成器/ xrange对象的构造计时 - 您 NOT 计算实际迭代所需的时间值 1 。有些原因导致在某些情况下创建生成器可能比创建xrange对象更快......

  1. 对于生成器案例,您只需创建一个生成器 - 生成器中的代码实际上没有运行。这相当于大约1个函数调用。
  2. 对于xrange案例,您正在调用函数,那么您必须查找全局名称xrange,全局TOTAL和那么你需要调用内置函数 - 所以 在这种情况下会执行更多的事情。
  3. 至于内存 - 在两种惰性方法中,使用的内存将由python运行时控制 - 而不是由生成器对象的大小决定。内存使用受到脚本影响的唯一情况是构建一个包含1亿个项目的列表。

    另请注意,我实际上无法在我的系统上一致地确认您的结果...使用timeit,我实际上得到my_xrange 有时 2 构建速度更快(约30%)。

    将以下内容添加到脚本底部:

    from timeit import timeit
    print timeit('my_xrange()', setup='from __main__ import my_xrange')
    print timeit('my_sequence()', setup='from __main__ import my_sequence')
    

    我的结果是(对于OS-X El-Capitan上的CPython):

    0.227491140366
    0.356791973114
    

    然而,pypy似乎更倾向于生成器构造(我首先尝试使用my_xrangemy_sequence,并且得到了相当一致的结果,尽管第一个运行似乎确实如此有点不利 - 可能是由于JIT预热时间或其他原因):

    0.00285911560059
    0.00137305259705
    

    1 在这里,我希望期待 xrange有优势 - 但在你timeit之前,没有任何事情是真的。然后,只有在时间差异显着的情况下才会出现这种情况,并且只有在您完成计时的计算机上才会出现这种情况。
    2 见开放免责声明:-P

答案 1 :(得分:3)

正如我在上面的评论中提到的,使用你的生成器函数和xrange,你实际上并没有创建序列,只是创建了对象。 @ mgilson的答案涵盖了与创建相关的电话。

至于与他们实际做某事:

>>> TOTAL = 100000
>>> # your functions here
...
>>> import timeit
>>> timeit.timeit("list(my_seq())", setup="from __main__ import my_seq", number=1000)
9.783777457339898
>>> timeit.timeit("list(my_xrange())", setup="from __main__ import my_xrange", number=1000)
1.2652621698083024
>>> timeit.timeit("list(my_list())", setup="from __main__ import my_list", number=1000)
2.666709824464867
>>> timeit.timeit("my_list()", setup="from __main__ import my_list", number=1000)
1.2324339537661615
  1. 你会看到我正在创建list,所以我正在处理序列。

  2. 生成器功能几乎是xrange的10倍。

  3. list(my_list)是多余的,因为my_list已经返回range生成的列表,所以我再次执行了此操作而没有调用list()

  4. rangexrange几乎相同,但那是因为我减少了TOTAL。最大的区别是range将占用更多内存,因为它首先创建整个列表,因此仅在该部分中更长。有效地从xrange = range创建列表。所以最终使用的内存是相同的,因为我只是在xrange中创建一个列表,所以很难看出这个简单案例的区别。