最近,我对iterator
,list comprehension
,iter(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
发电机比其他东西慢。 我不知道什么时候有用?
答案 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已经提到的一样,这个列表对于性能更好,但是当内存大小不够时,list
与n
太高的总和会使计算机变慢,但总和为{ {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/
这里有一些只有生成器可以提供的东西,而不是迭代器/迭代器的便捷快捷方式。