2015-07-10 91 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谢谢!我也帮助自己,所以双赢! –

相关问题