2016-02-04 68 views
3

我的印象是使用總和構造比運行for循環要快得多。然而,在下面的代碼中,for循環實際運行速度更快:Python中「總和」理解的速度

import time 

Score = [[3,4,5,6,7,8] for i in range(40)] 

a=[0,1,2,3,4,5,4,5,2,1,3,0,5,1,0,3,4,2,2,4,4,5,1,2,5,4,3,2,0,1,1,0,2,0,0,0,1,3,2,1] 

def ver1(): 
    for i in range(100000): 
     total = 0 
     for j in range(40): 
      total+=Score[j][a[j]] 
    print (total) 

def ver2(): 
    for i in range(100000): 
     total = sum(Score[j][a[j]] for j in range(40)) 
    print (total) 


t0 = time.time() 
ver1() 
t1 = time.time() 
ver2() 
t2 = time.time() 

print("Version 1 time: ", t1-t0) 
print("Version 2 time: ", t2-t1) 

輸出是:

208 
208 
Version 1 time: 0.9300529956817627 
Version 2 time: 1.066061019897461 

難道我做錯了什麼?有沒有辦法做得更快?

(請注意,這只是一個演示中,我設置了,在我的實際應用中的成績將不會以這種方式重複)

一些附加信息:這是關於Python 3.4.4 64位運行,在Windows 7 64位上,在i7上。

+0

[this question](http:// stackoverflow。com/questions/24578896/python-built-in-sum-function-vs-for-loop-performance)說'sum'應該比'for'循環快得多。 – Barmar

+0

我認爲你的瓶頸是列表理解,而不是加法。 – Barmar

+1

@Barmar在這兩個函數中都沒有列表理解。爲什麼一個*生成器理解*是一個瓶頸,因爲它和for循環完全相同?我認爲這可能只是函數調用'sum'的開銷,因爲範圍非常小... – L3viathan

回答

1

由於j被迭代兩個列表,我想我會看看是否拉鍊工作更好:

def ver3(): 
    for i in range(100000): 
     total = sum(s[i] for s,i in zip(Score,a)) 
    print (total) 

在此的Py2運行速度比2.0版本要慢30%左右,但對PY3約20%的速度如果我將zip更改爲izip(從itertools導入),則會將時間縮短到版本1和版本2之間。

+1

哎呀,如果你想變得聰明並且在'sum':'from operator import getitem','total = sum(map(getitem,Score,a))'''''可能會做得更好(在Py2上,使用'itertools.imap'來避免中間'list')。 – ShadowRanger

2

這似乎取決於系統,可能是python版本。在我的系統,不同的是約13%:

python sum.py 
208 
208 
('Version 1 time: ', 0.6371259689331055) 
('Version 2 time: ', 0.7342419624328613) 

兩個版本沒有測量sum與手動循環,因爲循環的「身體」是不相同的。 ver2做了更多的工作,因爲它創建了100000次生成器表達式,而ver1的循環體幾乎是微不足道的,但它爲每次迭代創建了一個包含40個元素的列表。你可以改變的例子是相同的,然後你看到的sum效果:

def ver1(): 
    r = [Score[j][a[j]] for j in range(40)] 
    for i in xrange(100000): 
     total = 0 
     for j in r: 
      total+=j 
    print (total) 

def ver2(): 
    r = [Score[j][a[j]] for j in xrange(40)] 
    for i in xrange(100000): 
     total = sum(r) 
    print (total) 

我搬到一切從內循環體的進出sum通話,以確保我們只測量手工製作的循環的開銷。使用xrange而不是range可進一步改善整體運行時間,但這適用於兩個版本,因此不會更改比較。我的系統上修改後的代碼的結果是:

python sum.py 
208 
208 
('Version 1 time: ', 0.2034609317779541) 
('Version 2 time: ', 0.04234910011291504) 

ver2ver1快5倍。這是使用sum而不是手工製作的循環的純粹性能增益。

ShadowRanger's comment on the question about lookups啓發,我已修改例子來比較的原代碼,並檢查是否結合的符號的查找:

def gen(s,b): 
    for j in xrange(40): 
     yield s[j][b[j]] 

def ver2(): 
    for i in range(100000): 
     total = sum(gen(Score, a)) 
    print (total) 

創建局部地結合Scorea防止昂貴查找一個小的自定義發生器在父範圍內。執行此操作:

python sum.py 
208 
208 
('Version 1 time: ', 0.6167840957641602) 
('Version 2 time: ', 0.6198039054870605) 

單獨的符號查找佔運行時間的約12%。

+0

我不認爲這是一個公平的比較。關鍵是找到計算總和的最快方法,通過從循環中刪除中間列表,您基本上已預先計算了總和的一部分,並且只顯示在平面列表上使用「總和」比迭代快。 – CaptainCodeman

+1

@CaptainCodeman如果你想比較'sum'和hadn-written循環的速度,你應該把所有的東西都拿出來。否則,你正在比較蘋果和橘子。否則,我會強烈地爭辯說,你的ver2代碼只是次優。 – Jens

+0

如果你只是想要一個平面數組,那麼肯定,「總和」會更快,但這從來沒有問題。這是測試它可能會出現一個更復雜的應用程序。在實踐中無法使陣列變平。 – CaptainCodeman