为什么这个生成表达式的性能比列表推导式更差?
我在找一种最快的方法来计算列表中符合特定条件的项目数量。这里的条件是找出列表中有多少个奇数。
在这个过程中,我对列表推导式和相应的生成器表达式的比较结果感到很惊讶:
python -m timeit -s "L = xrange(1000000)" "sum([1 for i in L if i & 1])"
10 loops, best of 3: 109 msec per loop
python -m timeit -s "L = xrange(1000000)" "sum(1 for i in L if i & 1)"
10 loops, best of 3: 125 msec per loop
我还尝试过用普通列表L,大小也不同,但在所有情况下,列表推导式的表现都更好。
那么,生成器表达式到底在做什么,导致它比创建一个包含100万个项目的新列表的列表推导式要慢呢……?
(顺便说一下,我找到的最快方法是:x = 1; len(filter(x.__and__, L))
。我知道这样写代码会让小猫咪受伤,但我只是为了好玩。)
2 个回答
3
根据我记得的,生成器每次得到结果时都需要激活一个新的框架,而列表推导式只需要激活一个框架。列表推导式的额外开销主要是内存的消耗——在你的例子中是对整数的引用。如果每个项目都是一个新的实例,并且占用更多内存,那么情况可能会反过来。
更新:经过测试,确实是反过来了。
~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum([oint(1) for i in L if i & 1])"
10 loops, best of 3: 414 msec per loop
~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum(oint(1) for i in L if i & 1)"
10 loops, best of 3: 392 msec per loop
15
当可用的内存几乎没有限制时(这在小规模测试中通常是这样,但在实际问题中往往不是!),列表通常会比生成器更快。这是因为列表可以一次性分配内存,像一大堆一样(这样就不会出现内存碎片等问题),而生成器则需要额外的工作来避免这种“一大堆”的方式,它们需要保持堆栈状态,以便能够继续执行。
在实际程序中,使用列表还是生成器哪个更快,取决于具体的内存情况,包括内存碎片,这在“小规模测试”中几乎不可能准确重现。换句话说,如果你真的关心性能,就必须仔细测试你实际的程序,而不仅仅是那些“玩具”级别的小规模测试。