python生成器太慢而无法使用它。我为什么要用呢?什么时候?

时间:2018-03-14 10:57:47

标签: python performance generator list-comprehension

最近,我对iteratorlist comprehensioniter(list comprehension)generator中哪一项是最快的事情提出了疑问。 然后制作如下的简单代码。

n = 1000000
iter_a = iter(range(n))
list_comp_a = [i for i in range(n)]
iter_list_comp_a = iter([i for i in range(n)])
gene_a = (i for i in range(n))

import time
import numpy as np

for xs in [iter_a, list_comp_a, iter_list_comp_a, gene_a]:
    start = time.time()
    np.sum(xs)
    end = time.time()
    print((end-start)*100)

结果如下。

0.04439353942871094 # iterator
9.257078170776367 # list_comprehension
0.006318092346191406 # iterator of list_comprehension
7.491207122802734 # generator 

发电机比其他东西慢。 我不知道什么时候有用?

3 个答案:

答案 0 :(得分:9)

generators不会一次性将所有元素存储在内存中。他们yield一次一个,这种行为使他们的内存效率。因此,当内存是约束时,您可以使用它们。

答案 1 :(得分:0)

我想我也问了一个错误的问题。 在原始代码中,它不正确,因为np.sum不能正常工作。 np.sum(iterator)没有回复正确答案。所以,我改变了我的代码,如下所示。

n = 10000
iter_a = iter(range(n))
list_comp_a = [i for i in range(n)]
iter_list_comp_a = iter([i for i in range(n)])
gene_a = (i for i in range(n))

import time
import numpy as np
import timeit

for xs in [iter_a, list_comp_a, iter_list_comp_a, gene_a]:
    start = time.time()
    sum(xs)
    end = time.time()
    print("type: {}, performance: {}".format(type(xs), (end-start)*100))

然后,表现如下。 list的性能最好,迭代器不好。

type: <class 'range_iterator'>, performance: 0.021791458129882812
type: <class 'list'>, performance: 0.013279914855957031
type: <class 'list_iterator'>, performance: 0.02429485321044922
type: <class 'generator'>, performance: 0.13570785522460938

和@Kishor Pawar已经提到的一样,这个列表对于性能更好,但是当内存大小不够时,listn太高的总和会使计算机变慢,但总和为{ {1}} iterator太高了,也许它真的需要很多时间来计算,但并没有让计算机变慢。

对所有人来说。 当我必须计算大量数据时,生成器会更好。 但是,

答案 2 :(得分:0)

作为序言:你的整个基准测试都是完全错误的 - &#34; list_comp_a&#34; test不使用列表推导来测试列表的构造时间(也不是#34; iter_list_comp_a&#34; fwiw),并且使用iter()的测试大多不相关 - iter(iterable)是只是iterable.__iter__()的一个快捷方式,只有你想操纵迭代器本身才有用,这几乎是非常罕见的。

如果您希望获得一些有意义的结果,那么您希望进行基准测试的是列表推导的执行,生成器表达式和生成器函数。为了测试它们的执行,最简单的方法是将所有三个案例包装在函数中,一个执行一个列表理解,另外两个构建列表来自resp。生成器表达式和由生成器函数构建的生成器。在所有情况下,我都使用xrange作为真正的来源,因此我们只对有效差异进行基准测试。我们还使用timeit.timeit来做基准测试,因为它比手动弄乱time.time()更可靠,并且实际上是用于对小代码片段进行基准测试的pythonic标准规范方法。

import timeit
# py2 / py3 compat
try:
    xrange
except NameError:
    xrange = range

n = 1000

def test_list_comp():
    return [x for x in xrange(n)]

def test_genexp():
    return list(x for x in xrange(n))

def mygen(n):
    for x in xrange(n):
        yield x

def test_genfunc():
    return list(mygen(n))

for fname in "test_list_comp", "test_genexp", "test_genfunc":
    result = timeit.timeit("fun()", "from __main__ import {} as fun".format(fname), number=10000)
    print("{} : {}".format(fname, result))

这里(5岁以上标准桌面上的py 2.7.x)我得到以下结果:

test_list_comp : 0.254354953766
test_genexp : 0.401108026505
test_genfunc : 0.403750896454

正如您所看到的,列表推导更快,生成器表达式和生成器函数大多等同于生成器表达式的非常小的(但如果重复测试则是常量)。

现在回答你的主要问题&#34;为什么以及何时使用生成器&#34;,答案有三个:1 /内存使用,2 /无限迭代和3 /协程。

第一点:内存使用。实际上,你不需要这里的生成器,只需要懒惰的迭代,这可以通过writing your own iterable / iterable获得 - 例如内置file类型 - 以避免在内存中加载所有内容只能动态生成值。这里生成器表达式和函数(以及底层generator类)是实现惰性迭代的通用方法,无需编写自己的iterable / iterator(就像内置property类是使用自定义{的一般方法一样{1}}没有你自己的描述符类)。

第二点:无限迭代。在这里,我们可以从序列类型(列表,元组,集合,字符串,字符串等)获得一些东西,根据定义,它们是有限的。一个例子是the itertools.cycle iterator

  

从迭代中返回元素,直到它耗尽为止。   然后无限期地重复序列。

请注意,此处此功能不是来自生成器函数或表达式,而是来自iterable / iterator协议。无限迭代的用例明显少于内存使用优化的用例,但在需要时它仍然是一个方便的功能。

最后第三点:协同程序。嗯,这是一个相当复杂的概念,特别是你第一次阅读它时,所以我会让其他人做介绍:https://jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/

这里有一些只有生成器可以提供的东西,而不是迭代器/迭代器的便捷快捷方式。