2010-04-10 29 views
11

在Python中倡導使用for i in xrange(...)風格的循環結構的基本原理是什麼?對於簡單的整數循環,開銷的差異是相當大的。我進行使用兩段代碼一個簡單的測試:Python首選語法背後的理由

文件idiomatic.py

#!/usr/bin/env python 

M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    for x in xrange(N): 
     for y in xrange(M): 
      pass 

文件cstyle.py

#!/usr/bin/env python 

M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    while x < N: 
     while y < M: 
      y += 1 
     x += 1 

剖析結果如下:

bash-3.1$ time python cstyle.py 

real 0m0.109s 
user 0m0.015s 
sys  0m0.000s 

bash-3.1$ time python idiomatic.py 

real 0m4.492s 
user 0m0.000s 
sys  0m0.031s 

我可以理解爲什麼Pythonic版本更慢 - 我想象它與調用xrange有很多關係,如果有一種方法可以倒回發生器,也許這可以被消除。但是,通過這種執行時間的差異,爲什麼會喜歡使用Pythonic版本?

編輯:我進行再次使用泰利先生提供的代碼測試,結果確實不如現在:

我想我會從這裏線程枚舉結論:

1)即使代碼包含在if __name__ == "__main__":塊中,模塊範圍內的大量代碼也是一個壞主意,

2)*奇怪的是,修改屬於thebadone我的版本不正確的代碼(設爲y成長過程中沒有復位)產生的性能差別不大,甚至對於較大的M值和N

+2

我認爲你的時機有缺陷。運行多個試驗,也許有一些實際執行的計算,以擺脫循環中的任何可能的優化 – Yuliy 2010-04-10 01:21:52

+0

+1非常有趣的問題。在閱讀Martinelli的回答後,這個問題對我來說更加有趣,因爲它顯示了調用函數內外的代碼之間的細微差別。 – OscarRyz 2010-04-10 02:10:20

+1

-1:由於該問題的基礎是從根本上不正確的代碼,請您關閉該問題。 – 2010-04-10 02:45:12

回答

22

下面是適當的比較,例如,在loop.py:

M = 10000 
N = 10000 

def thegoodone(): 
    for x in xrange(N): 
     for y in xrange(M): 
      pass 

def thebadone(): 
    x = 0 
    while x < N: 
     y = 0 
     while y < M: 
      y += 1 
     x += 1 

幾乎所有的代碼應該始終在功能 - 把一個億的循環在一個模塊的頂級顯示性能完全不顧及作出的任何嘗試的嘲弄測量表示。

一旦你做到了這一點,你會看到:

$ python -mtimeit -s'import loop' 'loop.thegoodone()' 
10 loops, best of 3: 3.45 sec per loop 
$ python -mtimeit -s'import loop' 'loop.thebadone()' 
10 loops, best of 3: 10.6 sec per loop 

所以,適當的衡量,你主張糟糕的辦法是比Python的促進好辦法慢3倍左右。我希望這會讓你重新考慮你錯誤的倡導。

+4

「所有實質性的代碼應該始終在功能上 - 在模塊的頂層放置一億個循環顯示對性能的魯莽忽視」(我明白了)。你能解釋一下爲什麼嗎? – 2010-04-10 01:52:10

+6

@Federico,**速度**。變量get和set在函數中高度優化,但不可能位於模塊頂層。例如,假設我的筆記本電腦和Glenn的機器是相同的,但從我們的數字來看,以正確的方式(功能中的所有實質性代碼)與完全錯誤的方式(模塊頂級的實質性代碼)進行正確的處理有2倍的差異。基準它自己! - ) – 2010-04-10 01:57:18

+1

我只是做了。在模塊級別:14.3s與36.0s。本地功能:8.6s與18.5s。 (!!)我不知道,謝謝。 – 2010-04-10 02:06:08

11

你忘了在內部循環後將y重置爲0。

#!/usr/bin/env python 
M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    while x < N: 
     while y < M: 
      y += 1 
     x += 1 
     y = 0 

編輯:20.63s後使用的xrange

+0

我正要回答類似的問題。我校正的while循環代碼比for循環代碼慢大約3倍。 – 2010-04-10 01:22:58

+1

嚴肅地說,沒有。語言習語總是要考慮到合理的表現;如果xrange成語真的*慢了40倍,那麼這將是一個有缺陷的成語,應該是固定的或者不再使用。可讀性很重要,通常甚至犧牲一些性能 - 但並不那麼重要。 – 2010-04-10 01:27:20

+0

確定刪除我的答案無論如何... – 2010-04-10 01:30:44

3

好用於遍歷數據結構

for i in ...語法是偉大的遍歷數據結構的修復與6.97s。在較低級別的語言中,您通常會遍歷由int索引的數組,但使用python語法可以消除索引步驟。

0

我重複了@Alex Martelli's answer的測試。慣用的環比while循環更快次:

python -mtimeit -s'from while_vs_for import while_loop as loop' 'loop(10000)' 
10 loops, best of 3: 9.6 sec per loop 
python -mtimeit -s'from while_vs_for import for_loop as loop' 'loop(10000)' 
10 loops, best of 3: 1.83 sec per loop 

while_vs_for.py

def while_loop(N): 
    x = 0 
    while x < N: 
     y = 0 
     while y < N: 
      pass 
      y += 1 
     x += 1 

def for_loop(N): 
    for x in xrange(N): 
     for y in xrange(N): 
      pass 

在模塊級:

$ time -p python for.py 
real 4.38 
user 4.37 
sys 0.01 
$ time -p python while.py 
real 14.28 
user 14.28 
sys 0.01 
1

這不是直接回答這個問題,但我想打開對話框xrange()。兩件事情:

A.有一些錯誤與OP陳述,沒有人(在不進行重置y的代碼是,除了BUG)尚未糾正的一個:

「我想象有很多事情要做調用x範圍N次......」

不像傳統計數for循環,Python的更像是一個外殼的foreach ...遍歷一個可迭代。因此,xrange()被稱爲恰好一次,而不是「N次」。

B. xrange()是Python 2中此函數的名稱。它將在Python 3中替換並重命名爲range(),因此在移植時請記住這一點。如果你還不知道,xrange()返回一個迭代器(類似於object),而range()返回列表。由於後者效率較低,因此已棄用xrange(),這對記憶更友好。對於所有需要列出的人,Python 3中的解決方法是list(range(N))

+0

我認爲每次內循環完成其所有迭代時,xrange(M)對象再次被定義,因爲AFAIK生成器不能倒帶。 – susmits 2010-04-11 03:24:52