问题 为什么这个生成器表达式函数比循环版本慢?


我一直在理论上运行生成器表达式比普通循环更有效。但后来我遇到了以下示例:编写一个给出数字的函数, 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倍甚至不接近!为什么?我有什么误会?


7831
2017-09-03 17:44


起源

你有 生成器表达式,而不是列表理解。 - Martijn Pieters♦
@MartijnPieters谢谢!显然我不是蟒蛇人:) - Barry


答案:


首先:生成器表达式 记忆 高效,不一定速度有效。

你的紧凑 genexp() 版本较慢有两个原因:

  • 生成器表达式使用新范围(如新函数)实现。你在制作 ñ 每个新的范围 any() 测试。创建一个新的范围并再次将其拆除是相对昂贵的,当然在循环中完成,然后与不执行此操作的代码进行比较。

  • sum() 和 any() 名称是要查找的其他全局变量。如果是 any(),这是一个额外的 ñ 每次测试的全局查找。必须在字典中查找全局变量,而不是通过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 name被生成器表达式代码对象查找为闭包而不是本地,它不像全局查找那样慢,但也不像本地查找那么快。


11
2017-09-03 17:47