为什么列表理解可以比Python中的map()更快? [英] Why list comprehension can be faster than map() in Python?
问题描述
我正在研究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.
列出理解的清单:
- 建立一个匿名的空列表.
- 加载
range
. - 加载
100
. - 致电
range
. - 获取迭代器.
- 获取该迭代器的下一项.
- 将该项目存储到
i
. - 加载
i
. - 加载整数5.
- 乘以五.
- 追加列表.
- 重复步骤6-10,直到范围为空.
- 将
l
指向匿名空列表.
- Build an anonymous empty list.
- Load
range
. - Load
100
. - Call
range
. - Get the iterator.
- Get the next item on that iterator.
- Store that item onto
i
. - Load
i
. - Load the integer five.
- Multiply times five.
- Append the list.
- Repeat steps 6-10 until range is empty.
- Point
l
to the anonymous empty list.
For循环的清单:
- 建立一个匿名的空列表.
- 将
l
指向匿名空列表. - 设置循环.
- 加载
range
. - 加载
100
. - 致电
range
. - 获取迭代器.
- 获取该迭代器的下一项.
- 将该项目存储到
i
. - 加载列表
l
. - 在该列表上加载属性
append
. - 加载
i
. - 加载整数5.
- 乘以五.
- 致电
append
. - 转到顶部.
- 绝对.
- Build an anonymous empty list.
- Point
l
to the anonymous empty list. - Setup a loop.
- Load
range
. - Load
100
. - Call
range
. - Get the iterator.
- Get the next item on that iterator.
- Store that item onto
i
. - Load the list
l
. - Load the attribute
append
on that list. - Load
i
. - Load the integer five.
- Multiply times five.
- Call
append
. - Go to the top.
- 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屋!