2011-03-25 14 views
5

我想看看比使用for循環進行簡單的數值運算要快多少。這是我發現的(使用標準timeit庫):用for循環比用reduce更快地求和?

In [54]: print(setup) 
from operator import add, iadd 
r = range(100) 

In [55]: print(stmt1)  
c = 0 
for i in r: 
    c+=i   

In [56]: timeit(stmt1, setup) 
Out[56]: 8.948904991149902 
In [58]: print(stmt3)  
reduce(add, r)  

In [59]: timeit(stmt3, setup) 
Out[59]: 13.316915035247803 

尋找多一點:

In [68]: timeit("1+2", setup) 
Out[68]: 0.04145693778991699 

In [69]: timeit("add(1,2)", setup) 
Out[69]: 0.22807812690734863 

這是怎麼回事?很明顯,reduce的循環速度要快於,但函數調用似乎占主導地位。 Reduce版本不應該幾乎完全在C中運行?在for循環版本中使用iadd(c,i)使其運行約24秒。爲什麼使用operator.add比+慢得多?我在印象+和operator.add運行相同的C代碼(我檢查,以確保operator.add不只是在Python或任何東西中調用+)。

順便說一句,只是使用和運行在〜2.3秒。

In [70]: print(sys.version) 
2.7.1 (r271:86882M, Nov 30 2010, 09:39:13) 
[GCC 4.0.1 (Apple Inc. build 5494)] 
+0

使用'sum'做這項工作快4倍的事實的結果是相當多的指標,「應該做一個明顯的方式」。 – jsbueno 2011-03-25 18:42:53

+0

@jsbbueno:是的,但我這樣做是爲了找出在序列上進行一般數字計算的最快方法。在現實世界中,我肯定會用sum來總結:D沒有嘗試過mul,但我確定結果是相似的。 – 2011-03-26 02:56:24

回答

5

操作reduce(add, r)必須調用add()功能100倍,因此函數調用的開銷加起來 - 減少使用PyEval_CallObject在每個迭代調用add

for (;;) { 
    ... 
    if (result == NULL) 
     result = op2; 
    else { 
     # here it is creating a tuple to pass the previous result and the next 
     # value from range(100) into func add(): 
     PyTuple_SetItem(args, 0, result); 
     PyTuple_SetItem(args, 1, op2); 
     if ((result = PyEval_CallObject(func, args)) == NULL) 
      goto Fail; 
    } 

更新:響應闕在評論中提到。

當你鍵入Python源代碼1 + 2,字節碼編譯器執行到位添加和替換3的表達:如果您添加兩個變量a + b編譯器將生成字節碼加載兩個

f1 = lambda: 1 + 2 
c1 = byteplay.Code.from_code(f1.func_code) 
print c1.code 

1   1 LOAD_CONST   3 
      2 RETURN_VALUE   

變量,並執行BINARY_ADD,這遠比調用函數來執行加法更快:如果減小用於添加一起NumPy的AR

f2 = lambda a, b: a + b 
c2 = byteplay.Code.from_code(f2.func_code) 
print c2.code 

1   1 LOAD_FAST   a 
      2 LOAD_FAST   b 
      3 BINARY_ADD   
      4 RETURN_VALUE   
+0

感謝您指出這一點!然而,它並不能解釋爲什麼原始的'1 + 2'比'add(1,2)'快5倍。事實上,減少速度遠遠快於在for中使用iadd時的速度。 – 2011-03-26 02:53:55

+0

我更新了更詳細的回覆。 – samplebias 2011-03-26 14:18:32

+1

爲什麼您的示例使用第三方包而不是內置的'dis'模塊? – 2011-03-26 16:08:14

0

這可能是複製args來開銷和返回值(即,「添加(1,2)」),而不是簡單地對數字文字

0

它可以比一個for循環更快。

from functools import reduce 
from numpy import array, arange 
from time import time 

def add(x, y): 
    return x + y 

def sum_columns(x): 
    if x.any(): 
     width = len(x[0]) 
     total = array([0] * width) 
    for row in x: 
     total += array(row) 
    return total 

l = arange(3000000) 
l = array([l, l, l]) 

start = time() 
print(reduce(add, l)) 
print('Reduce took {}'.format(time() - start)) 

start = time() 
print(sum_columns(l)) 
print('For loop took took {}'.format(time() - start)) 

隨着

[  0  3  6 ..., 8999991 8999994 8999997] 
Reduce took 0.024930953979492188 
[  0  3  6 ..., 8999991 8999994 8999997] 
For loop took took 0.3731539249420166