Python 用于循环项目解包
有一次,在看了Mike Muller的性能优化教程(我想)后,我的脑海中开始浮现出一个想法:如果性能很重要,那么通过索引最小化对循环中项目的访问,例如。G如果您需要在循环中多次访问Python 用于循环项目解包,python,performance,for-loop,iterable-unpacking,Python,Performance,For Loop,Iterable Unpacking,有一次,在看了Mike Muller的性能优化教程(我想)后,我的脑海中开始浮现出一个想法:如果性能很重要,那么通过索引最小化对循环中项目的访问,例如。G如果您需要在循环中多次访问x[1],对于l中的x,请为x[1]分配一个变量,并在循环中重复使用它 现在我有一个合成的例子: import timeit SEQUENCE = zip(range(1000), range(1, 1001)) def no_unpacking(): return [item[0] + item[1] f
x[1]
,对于l中的x,请为x[1]
分配一个变量,并在循环中重复使用它
现在我有一个合成的例子:
import timeit
SEQUENCE = zip(range(1000), range(1, 1001))
def no_unpacking():
return [item[0] + item[1] for item in SEQUENCE]
def unpacking():
return [a + b for a, b in SEQUENCE]
print timeit.Timer('no_unpacking()', 'from __main__ import no_unpacking').timeit(10000)
print timeit.Timer('unpacking()', 'from __main__ import unpacking').timeit(10000)
unpacking()
和no_unpacking()
函数返回相同的结果。实现是不同的:unpacking()
将项目解包到循环中的a
和b
no_unpacking()
通过索引获取值
对于python27,它显示:
1.25280499458
0.946601867676
换句话说,unpacking()
的性能比no\u unpacking()
高出约25%
问题是:
- 为什么通过索引访问会显著降低速度(即使在这种简单的情况下)
- 我也在
上尝试过这一点-从性能上看,这两个函数几乎没有区别。为什么呢pypy
感谢您的帮助。要回答您的问题,我们可以使用
dis
模块检查这两个函数生成的字节码:
In [5]: def no_unpacking():
...: s = []
...: for item in SEQUENCE:
...: s.append(item[0] + item[1])
...: return s
...:
...:
...: def unpacking():
...: s = []
...: for a,b in SEQUENCE:
...: s.append(a+b)
...: return s
我扩展了对列表的理解,因为在python3中,检查有趣的字节码会更麻烦。代码是等效的,因此它对我们的目的并不重要
第一个函数的字节码为:
In [6]: dis.dis(no_unpacking)
2 0 BUILD_LIST 0
3 STORE_FAST 0 (s)
3 6 SETUP_LOOP 39 (to 48)
9 LOAD_GLOBAL 0 (SEQUENCE)
12 GET_ITER
>> 13 FOR_ITER 31 (to 47)
16 STORE_FAST 1 (item)
4 19 LOAD_FAST 0 (s)
22 LOAD_ATTR 1 (append)
25 LOAD_FAST 1 (item)
28 LOAD_CONST 1 (0)
31 BINARY_SUBSCR
32 LOAD_FAST 1 (item)
35 LOAD_CONST 2 (1)
38 BINARY_SUBSCR
39 BINARY_ADD
40 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
43 POP_TOP
44 JUMP_ABSOLUTE 13
>> 47 POP_BLOCK
5 >> 48 LOAD_FAST 0 (s)
51 RETURN_VALUE
In [7]: dis.dis(unpacking)
9 0 BUILD_LIST 0
3 STORE_FAST 0 (s)
10 6 SETUP_LOOP 37 (to 46)
9 LOAD_GLOBAL 0 (SEQUENCE)
12 GET_ITER
>> 13 FOR_ITER 29 (to 45)
16 UNPACK_SEQUENCE 2
19 STORE_FAST 1 (a)
22 STORE_FAST 2 (b)
11 25 LOAD_FAST 0 (s)
28 LOAD_ATTR 1 (append)
31 LOAD_FAST 1 (a)
34 LOAD_FAST 2 (b)
37 BINARY_ADD
38 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
41 POP_TOP
42 JUMP_ABSOLUTE 13
>> 45 POP_BLOCK
12 >> 46 LOAD_FAST 0 (s)
49 RETURN_VALUE
注意,循环必须调用BINARY\u SUBSCR
两次才能访问元组的两个元素
第二个函数的字节码是:
In [6]: dis.dis(no_unpacking)
2 0 BUILD_LIST 0
3 STORE_FAST 0 (s)
3 6 SETUP_LOOP 39 (to 48)
9 LOAD_GLOBAL 0 (SEQUENCE)
12 GET_ITER
>> 13 FOR_ITER 31 (to 47)
16 STORE_FAST 1 (item)
4 19 LOAD_FAST 0 (s)
22 LOAD_ATTR 1 (append)
25 LOAD_FAST 1 (item)
28 LOAD_CONST 1 (0)
31 BINARY_SUBSCR
32 LOAD_FAST 1 (item)
35 LOAD_CONST 2 (1)
38 BINARY_SUBSCR
39 BINARY_ADD
40 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
43 POP_TOP
44 JUMP_ABSOLUTE 13
>> 47 POP_BLOCK
5 >> 48 LOAD_FAST 0 (s)
51 RETURN_VALUE
In [7]: dis.dis(unpacking)
9 0 BUILD_LIST 0
3 STORE_FAST 0 (s)
10 6 SETUP_LOOP 37 (to 46)
9 LOAD_GLOBAL 0 (SEQUENCE)
12 GET_ITER
>> 13 FOR_ITER 29 (to 45)
16 UNPACK_SEQUENCE 2
19 STORE_FAST 1 (a)
22 STORE_FAST 2 (b)
11 25 LOAD_FAST 0 (s)
28 LOAD_ATTR 1 (append)
31 LOAD_FAST 1 (a)
34 LOAD_FAST 2 (b)
37 BINARY_ADD
38 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
41 POP_TOP
42 JUMP_ABSOLUTE 13
>> 45 POP_BLOCK
12 >> 46 LOAD_FAST 0 (s)
49 RETURN_VALUE
请注意,没有要执行的二进制\u SUBSCR
因此,UNPACK\u SEQUENCE
加上一个STORE\u FAST
(这是解包添加的额外操作)似乎比执行两个BINARY\u SUBSCR
更快。
这是合理的,因为BINARY\u SUBSCR
是一个完整的方法调用,而UNPACK\u SEQUENCE
和STORE\u FAST
是更简单的操作
即使在更简单的情况下,您也可以看到差异:
In [1]: def iter_with_index(s):
...: for i in range(len(s)):
...: s[i]
...:
In [2]: def iter_without_index(s):
...: for el in s:el
...:
In [3]: %%timeit s = 'a' * 10000
...: iter_with_index(s)
...:
1000 loops, best of 3: 583 us per loop
In [4]: %%timeit s = 'a' * 10000
...: iter_without_index(s)
...:
1000 loops, best of 3: 206 us per loop
正如您所看到的,使用显式索引对字符串进行迭代大约要慢3倍。
这是由于调用BINARY\u SUBSCR
而产生的所有开销
关于第二个问题:pypy具有JIT,它能够分析代码并生成优化版本,从而避免索引操作的开销。
当它意识到订阅是在元组上完成的时,它可能能够生成不调用元组方法但直接访问元素的代码,从而完全删除
二进制\u SUBSCR
操作。很好的解释,很高兴知道我没有无缘无故地担心。回答很好。很彻底,解释得很好。然而,只要将range
更改为xrange
,我笔记本上的速度就从4.0us下降到了3.43us