我预计在多个循环的情况下,列表迭代要比使用生成器快得多,我的代码表明这是错误的。
我的理解是(操作是指定义元素的任何表达式):
我使用以下代码检查了我的期望:
from timeit import timeit
def pow2_list(n):
"""Return a list with powers of 2"""
results = []
for i in range(n):
results.append(2**i)
return results
def pow2_gen(n):
"""Generator of powers of 2"""
for i in range(n):
yield 2**i
def loop(iterator, n=1000):
"""Loop n times over iterable object"""
for _ in range(n):
for _ in iterator:
pass
l = pow2_list(1000) # point to a list
g = pow2_gen(1000) # point to a generator
time_list = \
timeit("loop(l)", setup="from __main__ import loop, l", number=10)
time_gen = \
timeit("loop(g)", setup="from __main__ import loop, g", number=10)
print("Loops over list took: ", time_list)
print("Loops over generator took: ", time_gen)结果让我吃惊..。
Loops over list took: 0.20484769299946493
Loops over generator took: 0.0019217690005461918不知何故,即使在循环1000次以上时,使用生成器的速度也比列表快得多。在这种情况下,我们讨论的是两个数量级!为什么?
编辑:
谢谢你的回答。现在我看到了我的错误。我错误地认为生成器从一个新的循环开始,就像range:
>>> x = range(10)
>>> sum(x)
45
>>> sum(x)
45但这太天真了(range不是发电机.)
关于可能重复的注释:我的问题涉及生成器上的多个循环,这在另一个线程中没有解释。
发布于 2016-11-25 14:57:19
你的发电机实际上只循环一次。一旦用pow2_gen创建,g就会存储一个生成器;这是第一次通过loop使用这个生成器,并释放出StopIteration。其他时候,通过loop,next(g) (或者Python2中的g.next() )只是继续抛出StopIteration,因此,实际上g代表了一个空序列。
为了使比较更加公平,每次循环时都需要重新创建生成器。
处理这个问题的另一个困难是调用append来构建列表,这可能是构建列表的最慢的方式。更多的情况是,列表是用列表理解来构建的。
下面的代码可以让我们更仔细地划分时间。create_list和create_gen分别使用列表理解和生成器表达式创建列表和生成器。time_loop就像您的loop方法,而time_apply是loop的一个版本,它每次通过循环重新创建可迭代性。
def create_list(n=1000):
return [2**i for i in range(n)]
def create_gen(n=1000):
return (2**i for i in range(n))
def time_loop(iterator, n=1000):
for t in range(n):
for v in iterator:
pass
def time_apply(create_fn, fn_arg, n=1000):
for t in range(n):
iterator = create_fn(fn_arg)
time_loop(iterator, 1)
print('time_loop(create_list): %.3f' % timeit("time_loop(create_list(1000))",
setup="from __main__ import *",
number=10))
print('time_loop(create_gen): %.3f' % timeit("time_loop(create_gen(1000))",
setup="from __main__ import *",
number=10))
print('time_apply(create_list): %.3f' % timeit("time_apply(create_list, 1000)",
setup="from __main__ import *",
number=10))
print('time_apply(create_gen): %.3f' % timeit("time_apply(create_gen, 1000)",
setup="from __main__ import *",
number=10))在我的框中的结果表明,构建一个列表(time_apply(create_list))在时间上与(甚至可能比)构建生成器(time_apply(create_gen))类似。
time_loop(create_list): 0.244
time_loop(create_gen): 0.028
time_apply(create_list): 21.190
time_apply(create_gen): 21.555您可以在问题中看到同样的效果,即time_loop(create_gen)比time_loop(create_list)快一个数量级。同样,这是因为创建的生成器仅被迭代一次,而不是列表上的多个循环。
正如您所假设的,在这个特定的场景中,构建一次列表并多次迭代它(time_loop(create_list))要比多次迭代生成器(time_apply(create_gen))要快。
列表和生成器之间的权衡将很大程度上取决于您正在创建的迭代器的大小。有了1000条商品,我希望列表会很快。有了100,000件物品,事情可能就不同了。
print('create big list: %.3f' % timeit("l = create_list(100000)",
setup="from __main__ import *",
number=10))
print('create big gen: %.3f' % timeit("g = create_gen(100000)",
setup="from __main__ import *",
number=10))我得到的是:
create big list: 209.748
create big gen: 0.023Python占用700至800 MB内存,用于构建大列表;生成器几乎不使用任何东西。在Python中,内存分配和垃圾清理在计算上是很昂贵的,可以预见会使代码变慢;生成器是一种非常简单的方法,可以避免占用机器的RAM,并且会对运行时产生很大的影响。
发布于 2016-11-25 15:06:13
你的考试有问题。也就是说,生成器是不可重用的。一旦筋疲力尽,就不能再使用它,必须生成一个新的。例如:
l = [0, 1, 2, 4, 5]
g = iter(l) # creates an iterator (a type of generator) over the list
sum_list0 = sum(l)
sum_list1 = sum(1)
assert sum_list0 == sum_list1 # all working normally
sum_gen0 = sum(g) # consumes generator
sum_gen1 = sum(g) # sum of empty generator is 0
assert sum_gen0 == sum_list1 # result is correct
assert sum_gen1 == sum_list1, "second result was incorrect" # because generator was exhausted要使测试正常运行,必须在传递给timeit的语句中重新创建生成器。
from timeit import timeit
n = 1000
repeats = 10000
list_powers = [2**i for i in range(n)]
def gen_powers():
for i in range(n):
yield 2**i
time_list = timeit("min(list_powers)", globals=globals(), number=repeats)
time_gen = timeit("min(gen_powers())", globals=globals(), number=repeats)
print("Loops over list took: ", time_list)
print("Loops over generator took: ", time_gen)给予:
Loops over list took: 0.24689035064701784
Loops over generator took: 13.551637053904571现在,发生器比列表慢两个数量级。这是预期的,因为与序列上的迭代次数相比,序列的大小很小。如果n很大,那么列表的创建就会变慢。这是因为在追加新项时如何展开列表,以及创建时未传递给列表的最终大小。与生成器相比,增加迭代次数将加快列表的速度,因为生成器所需的工作量增加,而对于列表,则保持不变。由于n只有1000 (小),而repeats占n的主导地位,那么生成器就会慢一些。
发布于 2016-11-25 15:00:41
您的测试无法工作,因为您的生成器在第一次通过loop()时就耗尽了。这是列表优于生成器的优点之一,您可以多次迭代它们(代价是将完整的列表存储在内存中)。
这是一个例子。我使用的是生成器表达式和列表理解(这比在for循环中使用for更优化),但概念是相同的:
>>> gen = (i for i in range(3))
>>> for n in range(2):
... for i in gen:
... print(i)
...
0 # 1st print
1
2 # after one loop the iterator is exhausted
>>>
>>> lst = [x for x in range(3)]
>>> for n in range(2):
... for i in lst:
... print(i)
...
0 # 1st print
1
2
0 # 2nd print
1
2
>>> 对于等效的测试,应该在外部循环的每一次迭代之后重新构建生成器:
>>> for n in range(2):
... gen = (i for i in range(3))
... for i in gen:
... print(i)
...
0 # 1st print
1
2
0 # 2nd print
1
2
>>>https://stackoverflow.com/questions/40807277
复制相似问题