为什么这个生成器表达式的性能比列表推导式差?

7

我试图找到一种最快的方法来计算符合特定过滤器条件的列表中的项目数量。在这种情况下,我要找出列表中有多少个奇数。

在执行此操作时,我对比较列表推导式和等效生成器表达式的结果感到惊讶:

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

我还尝试了使用普通列表和不同大小的列表,但在所有情况下,列表推导式都胜出。

那么,genexp是做了什么导致它比创建一个包含100万个项目的列表的listcomp慢呢?

(顺便说一句,我找到的最快方法是:x = 1; len(filter(x.__and__, L))。是的,我知道写这样的代码会杀死小猫咪,我只是为了好玩而已)

2个回答

15

当可用内存基本无限(这在小型基准测试中通常是这样,但在实际问题中通常不是这样!)时,列表将往往优于生成器,因为它们可以一次性分配,以一个“大块”(没有内存碎片等),而生成器需要(在内部)额外的工作来避免那种“大块”方法,通过保留堆栈帧状态来允许执行的恢复。

无论是列表方法还是生成器方法在真正的程序中哪个更快,取决于确切的内存情况,包括碎片化,这几乎不可能在“微基准测试”中准确复现。也就是说,最终,如果你真的关心性能,你必须仔细地对你实际的程序进行基准测试和分析,而不仅仅是一般的“玩具”微基准测试。


1+. 值得注意的是,由于生成器类似于流的特性,在许多情况下,它们可能会使用更少的内存。考虑将文件中的每一行读入列表并将其与逐行读取、处理和丢弃进行比较。 - Skurmedel

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接