为什么这个genexp表现比列表理解更差?

时间:2010-01-31 23:23:57

标签: python list-comprehension generator-expression

我试图找到最快的方法来计算匹配特定过滤器的列表中的项目数。 在这种情况下,查找列表中有多少个奇数。

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

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万个项目的新列表的listcomp相比,genexp正在做什么导致它更慢??

(顺便说一句,我找到的最快的方式是:x = 1; len(filter(x.__and__, L))。是的,我知道编写类似于杀死小猫的代码,我正是为了它的乐趣而这样做。)

2 个答案:

答案 0 :(得分:15)

当基本上无限的内存可用时(在微小的基准测试中总是如此,尽管通常不会出现在实际问题中! - ),列表往往会优于生成器,因为它们只能在一个“大”中分配一次束“(没有内存碎片等),而生成器需要(内部)额外的努力,通过保留堆栈帧状态以允许恢复执行来避免”大束“方法。

列表方法或生成器方法在实际程序中更快取决于确切的内存情况,包括碎片,这在“微基准”中几乎无法准确再现。 IOW,最后,如果你真正关心性能,你必须在一般情况下仔细地对(或单独地)描述你的实际程序进行基准测试,而不仅仅是“玩具”微基准测试。

答案 1 :(得分:3)

根据我的记忆,必须为每个结果激活生成器框架,而列表理解使用一个激活框架。列表压缩中的增量成本是内存的增加成本 - 在您的情况下引用int。如果每个项目都是一个新实例并使用更多内存,则该关系可能会反转。

更新:经过测试,它确实反转了

~% 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