为什么列表理解可以比Python中的map()更快? [英] Why list comprehension can be faster than map() in Python?

查看:218
本文介绍了为什么列表理解可以比Python中的map()更快?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究Python中类似结构的循环的性能问题,并发现以下语句:

I am looking in to the performance issues of the loop like structures in Python and found the following statements:

除了列表理解的句法优势外,它们通常 比等效使用地图快或快. (性能提示)

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

列表理解的运行速度比等效的for循环快(除非 您将只丢弃结果). ( Python速度)

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

我想知道引擎盖下的什么不同赋予了列表理解这一优势.谢谢.

I am wondering what difference under the hood gives list comprehension this advantage. Thanks.

推荐答案

测试一个:丢弃结果.

这是我们的虚拟功能:

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的问题,我不会分析其原始速度,而只是分析为什么.让我们看一下机器代码的差异.

I won't do an analysis of its raw speed, only why, per the OP's question. Lets take a look at the diffs of the machine code.

--- 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循环的步骤是建立一个循环.然后,它们都将继续加载全局range()(常数100),并为生成器调用range函数.然后它们都获得当前的迭代器并获得下一个项目,并将其存储到变量i中.然后他们加载examplefunc和i并调用examplefunc. Listcomp将其追加到列表,然后再次开始循环. For循环在三个指令中执行相同的操作,而不是两个.然后他们都加载None并返回它.

The race is on. Listcomp's first move is to build an empty list, while for loop's is to setup a loop. Both of them then proceed to load global range(), the constant 100, and call the range function for a generator. Then they both get the current iterator and get the next item, and store it into the variable i. Then they load examplefunc and i and call examplefunc. Listcomp appends it to the list and starts the loop over again. For loop does the same in three instructions instead of two. Then they both load None and return it.

那么,在这项分析中谁看起来更好?在这里,如果您不关心结果,则列表理解会执行一些多余的操作,例如构建列表并将其追加到列表中. For循环也非常有效.

So who seems better in this analysis? Here, list comprehension does some redundant operations such as building the list and appending to it, if you don't care about the result. For loop is pretty efficient too.

如果为它们计时,则使用for循环的时间比列表理解的速度快约三分之一. (在此测试中,examplefunc将其参数除以五,然后将其丢弃,而不是什么也不做.)

If you time them, using a for loop is about one-third faster than a list comprehension. (In this test, examplefunc divided its argument by five and threw it away instead of doing nothing at all.)

测试两项:保持结果正常.

此测试中没有虚拟功能.这就是我们的挑战者:

No dummy function this test. So here are our challengers:

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


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

差异对我们今天没有任何用处.只是两个块中的两个机器代码.

The diff isn't any use to us today. It's just the two machine codes in two blocks.

列出comp的机器代码:

List comp's machine code:

 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循环具有更少的指令.

As you can probably already tell, the list comprehension has fewer instructions than for loop does.

列出理解的清单:

  1. 建立一个匿名的空列表.
  2. 加载range.
  3. 加载100.
  4. 致电range.
  5. 获取迭代器.
  6. 获取该迭代器的下一项.
  7. 将该项目存储到i.
  8. 加载i.
  9. 加载整数5.
  10. 乘以五.
  11. 追加列表.
  12. 重复步骤6-10,直到范围为空.
  13. l指向匿名空列表.
  1. Build an anonymous empty list.
  2. Load range.
  3. Load 100.
  4. Call range.
  5. Get the iterator.
  6. Get the next item on that iterator.
  7. Store that item onto i.
  8. Load i.
  9. Load the integer five.
  10. Multiply times five.
  11. Append the list.
  12. Repeat steps 6-10 until range is empty.
  13. Point l to the anonymous empty list.

For循环的清单:

  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. 绝对.
  1. Build an anonymous empty list.
  2. Point l to the anonymous empty list.
  3. Setup a loop.
  4. Load range.
  5. Load 100.
  6. Call range.
  7. Get the iterator.
  8. Get the next item on that iterator.
  9. Store that item onto i.
  10. Load the list l.
  11. Load the attribute append on that list.
  12. Load i.
  13. Load the integer five.
  14. Multiply times five.
  15. Call append.
  16. Go to the top.
  17. Go to absolute.

(不包括以下步骤:加载None,将其返回.)

(Not including these steps: Load None, return it.)

列表理解不必执行以下操作:

The list comprehension doesn't have to do these things:

  • 每次加载列表的追加内容,因为它是作为本地变量预先绑定的.
  • 每个循环加载i两次
  • 花费两条说明到达顶部
  • 直接追加到列表中,而不用调用包装列表的包装器
  • Load append of the list every time, since it's pre-bound as a local variable.
  • Load i twice per loop
  • Spend two instructions going to the top
  • Directly append to the list instead of calling a wrapper that appens the list

总而言之,如果要使用这些值,listcomp会快很多,但是如果不使用,则相当慢.

In conclusion, listcomp is a lot faster if you are going to use the values, but if you don't it's pretty slow.

实际速度

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

Test one: for loop is faster by about one-third*

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

Test two: list comprehension is faster by about two-thirds*

*关于->精确到小数点第二位

*About -> second decimal place acurrate

这篇关于为什么列表理解可以比Python中的map()更快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆