11

我一直在使用generator的表達式比普通循環更有效。但後來我遇到了下面的例子:寫一個給定數字的函數,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

你有*生成器表達式*,而不是列表解析。 –

+0

@MartijnPieters謝謝!顯然,我不是一個蟒蛇人:) – Barry

回答

11

首先:發生器表達式是內存有效,不一定是速度有效的。

您的緊湊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名稱被查找爲閉包,而不是本地,由生成器表達式代碼對象查找,它不像全局查找那麼慢,但也不像本地查找那麼快。

相關問題