简体   繁体   English

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

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

I am looking in to the performance issues of the loop like structures in Python and found the following statements: 我正在研究Python中循环结构的性能问题,并发现以下语句:

Besides the syntactic benefit of list comprehensions, they are often as fast or faster than equivalent use of map. 除了列表推导的句法益处之外,它们通常比等效使用map更快或更快。 ( Performance Tips ) 性能提示

List comprehensions run a bit faster than equivalent for-loops (unless you're just going to throw away the result). 列表推导比等效的for循环运行得快一点(除非你只是扔掉结果)。 ( Python Speed ) Python速度

I am wondering what difference under the hood gives list comprehension this advantage. 我想知道引擎盖下有什么区别可以让列表理解这个优势。 Thanks. 谢谢。

Test one : throwing away the result. 测试一 :扔掉结果。

Here's our dummy function: 这是我们的虚拟功能:

def examplefunc(x):
    pass

And here are our challengers: 以下是我们的挑战者:

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

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

I won't do an analysis of its raw speed, only why , per the OP's question. 根据OP的问题,我不会对其原始速度进行分析,只是为什么 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     

The race is on. 比赛开始了。 Listcomp's first move is to build an empty list, while for loop's is to setup a loop. Listcomp的第一步是建立一个空列表,而for循环是建立一个循环。 Both of them then proceed to load global range(), the constant 100, and call the range function for a generator. 然后它们都继续加载全局范围(),常量100,并调用生成器的范围函数。 Then they both get the current iterator and get the next item, and store it into the variable i. 然后他们都获得当前的迭代器并获得下一个项目,并将其存储到变量i中。 Then they load examplefunc and i and call examplefunc. 然后他们加载examplefunc和i并调用examplefunc。 Listcomp appends it to the list and starts the loop over again. Listcomp将它附加到列表并再次开始循环。 For loop does the same in three instructions instead of two. For循环在三个指令中执行相同而不是两个。 Then they both load None and return it. 然后他们都加载None并返回它。

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循环也非常有效。

If you time them, using a for loop is about one-third faster than a list comprehension. 如果你计时,使用for循环比列表理解快三分之一。 (In this test, examplefunc divided its argument by five and threw it away instead of doing nothing at all.) (在这个测试中,examplefunc将其参数除以5并将其抛弃而不是什么都不做。)

Test two : Keeping the result like normal. 测试二 :保持结果正常。

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. 它只是两个块中的两个机器码。

List comp's machine code: 列出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        

For loop's machine code: 对于循环的机器代码:

 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        

As you can probably already tell, the list comprehension has fewer instructions than for loop does. 正如您可能已经知道的那样,列表理解的指令少于循环。

List comprehension's checklist: 列表理解的清单:

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

For loop's checklist: 对于循环的核对清单:

  1. Build an anonymous empty list. 构建一个匿名的空列表。
  2. Point l to the anonymous empty list. l指向匿名空列表。
  3. Setup a loop. 设置一个循环。
  4. Load range . 负载range
  5. Load 100 . 加载100
  6. Call range . 通话range
  7. Get the iterator. 获取迭代器。
  8. Get the next item on that iterator. 获取该迭代器上的下一个项目。
  9. Store that item onto i . 将该项目存储到i
  10. Load the list l . 加载列表l
  11. Load the attribute append on that list. 在该列表中加载属性append
  12. Load i . 加载i
  13. Load the integer five. 加载整数五。
  14. Multiply times five. 乘以五倍。
  15. Call append . 呼叫append
  16. Go to the top. 转到顶部。
  17. Go to absolute. 去绝对。

(Not including these steps: Load None , return it.) (不包括以下步骤:加载None ,返回。)

The list comprehension doesn't have to do these things: 列表理解不必执行以下操作:

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

In conclusion, listcomp is a lot faster if you are going to use the values, but if you don't it's pretty slow. 总之,如果要使用这些值,listcomp要快得多,但如果不这样做,那么速度非常慢。

Real speeds 真正的速度

Test one: for loop is faster by about one-third* 测试一:for循环速度快三分之一*

Test two: list comprehension is faster by about two-thirds* 测试二:列表理解速度提高了大约三分之二*

*About -> second decimal place acurrate *关于 - >小数点后第二位

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么带有列表理解语句的函数比列表理解语句*快*? - why is function with a list comprehension statement *faster* than the list comprehension statement? 为什么从pandas DataFrame中提取列作为列表并比使用map函数更快地执行列表理解? - Why is extracting a column from a pandas DataFrame as a list and performing list comprehension faster than using the map function? 为什么pandas映射比列表理解慢 - Why is pandas map slower than list comprehension 更快的Python列表理解 - Faster Python List Comprehension 为什么用python中的map()函数列表理解可以返回None - Why list comprehension with map() function in python can return None 为什么list.remove比列表理解更快? - Why is list.remove faster than list comprehension? 为什么列表理解比附加到列表快得多? - Why is a list comprehension so much faster than appending to a list? 为什么itertools.chain比扁平化列表理解更快? - Why is itertools.chain faster than a flattening list comprehension? 为什么这个列表理解比等效的生成器表达更快? - Why this list comprehension is faster than equivalent generator expression? 为什么列表理解比乘法数组的numpy要快得多? - Why list comprehension is much faster than numpy for multiplying arrays?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM