我一直在理论上运行,因为生成器表达式往往比普通循环更有效。但后来我遇到了以下示例:编写一个给出数字N
的函数,以及一些因子ps
,返回N
下所有数字的总和。至少有一个因素。
这是循环版本和更短的生成器表达式版本:
def loops(N, ps):
total_sum = 0
for i in xrange(N):
for p in ps:
if i%p == 0:
total_sum += i
break
return total_sum
def genexp(N, ps):
return sum(i for i in xrange(N)
if any(i%p == 0 for p in ps))
我希望两者的表现大致相同,理解版本可能会快一点,但我没想到的是:
for func in ('loops', 'genexp'):
print func, timeit.timeit('%s(100000, [3,5,7])' % func,
number=100,
setup='from __main__ import %s' % func)
loops 2.82878184319
genexp 10.1663100719
慢4倍甚至不接近!为什么?我误解了什么?
答案 0 :(得分:11)
首先:生成器表达式 memory 效率高,不一定是速度效率。
您的紧凑genexp()
版本速度较慢有两个原因:
使用新范围(如新功能)实现生成器表达式。您正在为每个any()
测试生成 N 个新范围。创建一个新的范围并再次将其拆除是相对昂贵的,当然是在循环中完成,然后与不执行此操作的代码进行比较。
sum()
和any()
名称是要查找的其他全局变量。对于any()
,每次测试都需要额外的 N 全局查找。全局变量必须在字典中查找,而不是通过C数组中的索引查找的本地人(这非常快)。
后者只是一个小组件,大部分成本在于创建和销毁框架(范围);如果您创建的版本_any
和_sum
是您获得的功能的本地人,但性能略有改善:
>>> def genexp_locals(N, ps, _any=any, _sum=sum):
... return _sum(i for i in xrange(N)
... if _any(i%p == 0 for p in ps))
...
>>> for func in ('loops', 'genexp', 'genexp_locals'):
... print func, timeit.timeit('%s(100000, [3,5,7])' % func,
... number=100,
... setup='from __main__ import %s' % func)
...
loops 2.00835800171
genexp 6.45241594315
genexp_locals 6.23843789101
我没有为xrange
创建本地,以保持相同的方面。从技术上讲,_any
名称被生成器表达式代码对象查找为闭包而不是本地,它不像全局查找那么慢,但也不像本地查找那么快。