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

我也尝试过将 L 作为常规列表,并且大小不同,但在所有情况下,列表理解都会获胜。

与创建包含 100 万个项目的新列表的 listcomp 相比, genexp 做了什么导致它变慢......?

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

4

2 回答 2

15

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

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

于 2010-01-31T23:29:29.997 回答
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
于 2010-01-31T23:31:59.827 回答