簡體   English   中英

為什么列表理解可能比Python中的map()更快?

[英]Why list comprehension can be faster than map() in Python?

我正在研究Python中循環結構的性能問題,並發現以下語句:

除了列表推導的句法益處之外,它們通常比等效使用map更快或更快。 性能提示

列表推導比等效的for循環運行得快一點(除非你只是扔掉結果)。 Python速度

我想知道引擎蓋下有什么區別可以讓列表理解這個優勢。 謝謝。

測試一 :扔掉結果。

這是我們的虛擬功能:

def examplefunc(x):
    pass

以下是我們的挑戰者:

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

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

根據OP的問題,我不會對其原始速度進行分析,只是為什么 讓我們來看看機器代碼的差異。

--- 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和i並調用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)

差異對我們今天沒用。 它只是兩個塊中的兩個機器碼。

列出comp的機器代碼:

 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        

對於循環的機器代碼:

 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        

正如您可能已經知道的那樣,列表理解的指令少於循環。

列表理解的清單:

  1. 構建一個匿名的空列表。
  2. 負載range
  3. 加載100
  4. 通話range
  5. 獲取迭代器。
  6. 獲取該迭代器上的下一個項目。
  7. 將該項目存儲到i
  8. 加載i
  9. 加載整數五。
  10. 乘以五倍。
  11. 附上清單。
  12. 重復步驟6-10,直到范圍為空。
  13. l指向匿名空列表。

對於循環的核對清單:

  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兩次
  • 花兩條指令到頂部
  • 直接附加到列表而不是調用附加列表的包裝器

總之,如果要使用這些值,listcomp要快得多,但如果不這樣做,那么速度非常慢。

真正的速度

測試一:for循環速度快三分之一*

測試二:列表理解速度提高了大約三分之二*

*關於 - >小數點后第二位

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM