2015-07-10 88 views
3

我期待在像在Python結構環路的性能問題,並找到下面的語句:爲什麼在Python中列表的理解速度可能比map()更快?

除了列表解析的語法利益,他們往往 一樣快或比同等使用地圖的速度更快。 (Performance Tips

列表解析運行速度比等效的for-loops快一點(除非 你只是要扔掉結果)。 (Python Speed

我想知道在引擎蓋下有什麼區別給出列表理解這一優勢。謝謝。

+0

http://blog.codinghorror.com/learn-to-read-the-source-luke/ –

+0

嗯,我總是使用列表解析,即使我完全拋棄結果... –

+4

@約翰科爾曼:/請這樣做,併發佈一個答案... –

回答

6

測試一:扔掉結果。

這裏是我們的虛擬函數:

def examplefunc(x): 
    pass 

這裏是我們的挑戰者:

def listcomp_throwaway(): 
    [examplefunc(i) for i in range(100)] 

def forloop_throwaway(): 
    for i in range(100): 
     examplefunc(i) 

我不會做它的原始速度的分析,只有爲什麼,每任擇議定書的問題。讓我們看看機器代碼的差異。

--- List comprehension 
+++ For loop 
@@ -1,15 +1,16 @@ 
- 55   0 BUILD_LIST    0 
+ 59   0 SETUP_LOOP    30 (to 33) 
       3 LOAD_GLOBAL    0 (range) 
       6 LOAD_CONST    1 (100) 
       9 CALL_FUNCTION   1 
       12 GET_ITER    
-  >> 13 FOR_ITER    18 (to 34) 
+  >> 13 FOR_ITER    16 (to 32) 
       16 STORE_FAST    0 (i) 
-    19 LOAD_GLOBAL    1 (examplefunc) 
+ 
+ 60   19 LOAD_GLOBAL    1 (examplefunc) 
       22 LOAD_FAST    0 (i) 
       25 CALL_FUNCTION   1 
-    28 LIST_APPEND    2 
-    31 JUMP_ABSOLUTE   13 
-  >> 34 POP_TOP    
-    35 LOAD_CONST    0 (None) 
-    38 RETURN_VALUE   
+    28 POP_TOP    
+    29 JUMP_ABSOLUTE   13 
+  >> 32 POP_BLOCK   
+  >> 33 LOAD_CONST    0 (None) 
+    36 RETURN_VALUE  

比賽開始。 Listcomp的第一步是構建一個空列表,而for循環則是設置一個循環。他們兩個然後繼續加載全局範圍(),常量100,並調用生成器的範圍函數。然後他們都獲得當前的迭代器並獲取下一個項目,並將其存儲到變量i中。然後他們加載examplefunc和我並調用examplefunc。 Listcomp將它追加到列表中,並再次啓動循環。 For循環在三條指令中執行相同的操作,而不是兩條。然後它們都加載None並將其返回。

那麼在這個分析中誰更好?在這裏,列表理解會執行一些冗餘操作,例如構建列表並追加到列表中,如果您不關心結果。 For循環也非常有效。

如果你計時,使用for循環大約比列表理解速度快三分之一。 (在這個測試中,examplefunc將它的參數劃分爲5並丟棄它,而不是什麼也不做。)

測試二:保持結果一樣正常。

這個測試沒有虛擬函數。所以這裏是我們的挑戰者:

def listcomp_normal(): 
    l = [i*5 for i in range(100)] 


def forloop_normal(): 
    l = [] 
    for i in range(100): 
     l.append(i*5) 

這個差異對我們今天沒有任何用處。這只是兩個塊中的兩個機器碼。

名單補償的機器代碼:

55   0 BUILD_LIST    0 
       3 LOAD_GLOBAL    0 (range) 
       6 LOAD_CONST    1 (100) 
       9 CALL_FUNCTION   1 
      12 GET_ITER    
     >> 13 FOR_ITER    16 (to 32) 
      16 STORE_FAST    0 (i) 
      19 LOAD_FAST    0 (i) 
      22 LOAD_CONST    2 (5) 
      25 BINARY_MULTIPLY  
      26 LIST_APPEND    2 
      29 JUMP_ABSOLUTE   13 
     >> 32 STORE_FAST    1 (l) 
      35 LOAD_CONST    0 (None) 
      38 RETURN_VALUE   

For循環的機器代碼:

59   0 BUILD_LIST    0 
       3 STORE_FAST    0 (l) 

60   6 SETUP_LOOP    37 (to 46) 
       9 LOAD_GLOBAL    0 (range) 
      12 LOAD_CONST    1 (100) 
      15 CALL_FUNCTION   1 
      18 GET_ITER    
     >> 19 FOR_ITER    23 (to 45) 
      22 STORE_FAST    1 (i) 

61   25 LOAD_FAST    0 (l) 
      28 LOAD_ATTR    1 (append) 
      31 LOAD_FAST    1 (i) 
      34 LOAD_CONST    2 (5) 
      37 BINARY_MULTIPLY  
      38 CALL_FUNCTION   1 
      41 POP_TOP    
      42 JUMP_ABSOLUTE   19 
     >> 45 POP_BLOCK   
     >> 46 LOAD_CONST    0 (None) 
      49 RETURN_VALUE   

正如你可能已經知道,在列表理解比for循環做較少的指令。

列表理解的清單:

  1. 建立一個匿名的空單。
  2. 加載range
  3. 加載100
  4. 致電range
  5. 獲取迭代器。
  6. 獲取該迭代器的下一個項目。
  7. 將該項目存儲到i
  8. 加載i
  9. 加載整數五。
  10. 乘以五。
  11. 附加列表。
  12. 重複步驟6-10,直到範圍爲空。
  13. 指向l到匿名空白列表。

For循環的清單:

  1. 建立一個匿名的空單。
  2. 指向l到匿名空白列表。
  3. 設置一個循環。
  4. 加載range
  5. 加載100
  6. 致電range
  7. 獲取迭代器。
  8. 獲取該迭代器的下一個項目。
  9. 將該項目存儲到i
  10. 加載列表l
  11. 在該列表中加載屬性append
  12. 加載i
  13. 加載整數五。
  14. 乘以五。
  15. 致電append
  16. 回到頂部。
  17. 轉到絕對。

(不包括下列步驟:加載None,其返回。)

列表內涵沒有做這些事情:

  • 每次名單的負載追加,因爲它被預先綁定爲局部變量。每個循環
  • 負載i兩次
  • 花兩個指令去頂端
  • 直接添加到列表中,而不是調用一個appens列表

最後的包裝,listcomp是快了很多,如果你將會使用這些值,但是如果你不這樣做,它會很慢。

真實速度

測試一個:for循環是由約三分之一*更快

測試二:列表理解是大約三分之二*

*關於快 - >小數點後第二位鼓勵

+1

非常感謝您的分析! – Bin

+0

@Bin謝謝!我也幫助自己,所以雙贏! –

相關問題