【发布时间】:2020-03-22 04:15:03
【问题描述】:
我正在使用 Python3.5 进行一些基准测试,并且在比较以下代码示例时,我注意到 f1 的运行速度比 f2 快 45% 以上:
def f1():
acc = 0
a = a_
for i in a[100:900]:
acc += i
return acc
def f2():
acc = 0
a = a_
for i in range(100, 900):
acc += a[i]
return acc
这对我来说有点违反直觉,因为for i in a[100:900] 看起来像是在执行不需要的数据副本。这在反汇编代码时通过BINARY_SUBSCR 操作码的存在得到了证实。这是字节码的相关部分:
9 12 SETUP_LOOP 34 (to 49)
15 LOAD_FAST 1 (a)
18 LOAD_CONST 2 (100)
21 LOAD_CONST 3 (900)
24 BUILD_SLICE 2
27 BINARY_SUBSCR
28 GET_ITER
>> 29 FOR_ITER 16 (to 48)
32 STORE_FAST 2 (i)
您如何解释f1 的出色表现?序列BINARY_SUBSCR、GET_ITER 是否以某种方式优化以避免数据复制?
下面是完整的测试代码供参考。我尝试将列表大小增加到 1_000_000 个项目,f1 仍然表现更好。使用array.array时也是如此。
a_ = list(range(1000))
def f1():
acc = 0
a = a_
for i in a[100:900]:
acc += i
return acc
def f2():
acc = 0
a = a_
for i in range(100, 900):
acc += a[i]
return acc
from dis import dis
from timeit import timeit
for f in f1,f2:
dis(f)
print(timeit(f, number=200000))
print()
结果:
6 0 LOAD_CONST 1 (0)
3 STORE_FAST 0 (acc)
7 6 LOAD_GLOBAL 0 (a_)
9 STORE_FAST 1 (a)
8 12 SETUP_LOOP 34 (to 49)
15 LOAD_FAST 1 (a)
18 LOAD_CONST 2 (100)
21 LOAD_CONST 3 (900)
24 BUILD_SLICE 2
27 BINARY_SUBSCR
28 GET_ITER
>> 29 FOR_ITER 16 (to 48)
32 STORE_FAST 2 (i)
9 35 LOAD_FAST 0 (acc)
38 LOAD_FAST 2 (i)
41 INPLACE_ADD
42 STORE_FAST 0 (acc)
45 JUMP_ABSOLUTE 29
>> 48 POP_BLOCK
11 >> 49 LOAD_FAST 0 (acc)
52 RETURN_VALUE
5.18372956989333
14 0 LOAD_CONST 1 (0)
3 STORE_FAST 0 (acc)
15 6 LOAD_GLOBAL 0 (a_)
9 STORE_FAST 1 (a)
16 12 SETUP_LOOP 37 (to 52)
15 LOAD_GLOBAL 1 (range)
18 LOAD_CONST 2 (100)
21 LOAD_CONST 3 (900)
24 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
27 GET_ITER
>> 28 FOR_ITER 20 (to 51)
31 STORE_FAST 2 (i)
17 34 LOAD_FAST 0 (acc)
37 LOAD_FAST 1 (a)
40 LOAD_FAST 2 (i)
43 BINARY_SUBSCR
44 INPLACE_ADD
45 STORE_FAST 0 (acc)
48 JUMP_ABSOLUTE 28
>> 51 POP_BLOCK
19 >> 52 LOAD_FAST 0 (acc)
55 RETURN_VALUE
8.191981540992856
【问题讨论】:
标签: python-3.x loops optimization cpython opcode