为什么在Python中列表理解可以比map()快?

2024-06-25 23:46:26 发布

您现在位置:Python中文网/ 问答频道 /正文

我在研究Python中循环类结构的性能问题时发现了以下语句:

Besides the syntactic benefit of list comprehensions, they are often as fast or faster than equivalent use of map. (Performance Tips)

List comprehensions run a bit faster than equivalent for-loops (unless you're just going to throw away the result). (Python Speed)

我想知道到底是什么区别让列表理解有了这个优势。谢谢。在


Tags: ofthe语句性能结构arelistthan
1条回答
网友
1楼 · 发布于 2024-06-25 23:46:26

测试一:丢弃结果。在

这是我们的虚拟函数:

def examplefunc(x):
    pass

以下是我们的挑战者:

^{pr2}$

我不会对它的原始速度进行分析,只会根据操作员的问题来分析它的原始速度。让我们看看机器代码的差异。在

 - 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循环的第一步是设置一个循环。然后,它们都继续加载global range(),常量100,并调用生成器的range函数。然后它们都获取当前迭代器和下一个项,并将其存储到变量i中,然后加载examplefunc和i并调用examplefunc。Listcomp将它追加到列表中,并重新开始循环。For循环在三条指令而不是两条指令中执行相同的操作。然后他们都不加载并返回。在

那么,在这个分析中,谁看起来更好呢?在这里,如果您不关心结果,list comprehension会执行一些冗余操作,例如构建列表并将其附加到列表中。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        

循环机器代码:

 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. 加载整数5。在
  10. 乘以五。在
  11. 附加列表。在
  12. 重复步骤6-10,直到范围为空。在
  13. l指向匿名空列表。在

For loop的检查表:

  1. 建立一个匿名的空列表。在
  2. l指向匿名空列表。在
  3. 设置一个循环。在
  4. 加载range。在
  5. 加载100。在
  6. 呼叫range。在
  7. 获取迭代器。在
  8. 获取迭代器上的下一项。在
  9. 将该项存储到i。在
  10. 加载列表l。在
  11. 在该列表中加载属性append。在
  12. 加载i。在
  13. 加载整数5。在
  14. 乘以五。在
  15. 呼叫append。在
  16. 到顶端去。在
  17. 转到绝对值。在

(不包括以下步骤:LoadNone,返回它。)

列表理解不必做这些事情:

  • 每次都加载列表的append,因为它是作为局部变量预先绑定的。在
  • 每个循环加载i两次
  • 花两个指令到顶端
  • 直接追加到列表中,而不是调用出现在列表上的包装器

总之,如果要使用这些值,listcomp要快得多,但如果不使用,则相当慢。在

实际速度

测试一:for循环快三分之一*

测试二:列表理解速度快三分之二*

*大约->;第二个小数位

相关问题 更多 >