正如user2357112指出的那样,原始的性能测试脚本并没有像我预期的那样工作。 "在第一次执行s1之后,你的列表中没有1,因此没有进一步执行s1并且没有执行s2实际上采用x == 1分支。" 修改后的版本:
import timeit
import random
random.seed(0)
a = [ random.randrange(10) for _ in range(10000)]
change_from = 1
change_to = 6
setup = "from __main__ import a, change_from, change_to"
# s1 is replaced for a simple for loop, which is faster than the original
s1 = """\
for i,x in enumerate(a):
if x == change_from:
a[i] = change_to
change_from, change_to = change_to, change_from
"""
s2 = """\
a = [change_to if x==change_from else x for x in a]
change_from, change_to = change_to, change_from
"""
print(timeit.timeit(stmt=s1,number=10000, setup=setup))
print(timeit.timeit(stmt=s2, number=10000, setup=setup))
此脚本替换每次出现的1到6,然后下一次出现每次出现的6到1.依此类推。结果是:
7.841739330212443
5.5166219217914065
为什么列表理解更快?
如何找出这类问题呢? 董事会的评论看起来很有意思,谢谢。
使用以下python版本: 在win32上的Python 3.6.0(v3.6.0:41df79263a11,2016年12月23日,08:06:12)[MSC v.1900 64位(AMD64)]
由于我没有得到详细的答案,我试图弄明白。
如果我使用此函数表示简单的for循环:
def func1(a):
for i,x in enumerate(a):
if x == 1:
a[i] = 6
return(a)
并拆解它,我得到以下内容:
func1:
7 0 SETUP_LOOP 48 (to 51)
3 LOAD_GLOBAL 0 (enumerate)
6 LOAD_FAST 0 (a)
9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 GET_ITER
>> 13 FOR_ITER 34 (to 50)
16 UNPACK_SEQUENCE 2
19 STORE_FAST 1 (i)
22 STORE_FAST 2 (x)
8 25 LOAD_FAST 2 (x)
28 LOAD_CONST 1 (1)
31 COMPARE_OP 2 (==)
34 POP_JUMP_IF_FALSE 13
9 37 LOAD_CONST 2 (6)
40 LOAD_FAST 0 (a)
43 LOAD_FAST 1 (i)
46 STORE_SUBSCR
47 JUMP_ABSOLUTE 13
>> 50 POP_BLOCK
10 >> 51 LOAD_FAST 0 (a)
54 RETURN_VALUE
这很简单。它遍历a,如果它找到值1,则用STORE_SUBSCR将其替换为6。
如果我用这个函数代表理解变体:
def func2(a): a = [6如果x == 1其他x代表x in a] 返回(a)中
并拆解它,我得到以下内容:
func2:
7 0 LOAD_CONST 1 (<code object <listcomp> at 0x00000000035731E0, file "<file_path>", line 7>)
3 LOAD_CONST 2 ('func2.<locals>.<listcomp>')
6 MAKE_FUNCTION 0
9 LOAD_FAST 0 (a)
12 GET_ITER
13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
16 STORE_FAST 0 (a)
8 19 LOAD_FAST 0 (a)
22 RETURN_VALUE
这比之前的要短。但是它从代码对象加载开始。 func2具有以下代码常量:
>>> func2.__code__.co_consts
(None, <code object <listcomp> at 0x00000000035731E0, file "<file_path>", line 7>, 'func2.<locals>.<listcomp>')
并且listcomp代码对象如下所示:
>>> dis.dis(func2.__code__.co_consts[1].co_code)
0 BUILD_LIST 0
3 LOAD_FAST 0 (0)
>> 6 FOR_ITER 30 (to 39)
9 STORE_FAST 1 (1)
12 LOAD_FAST 1 (1)
15 LOAD_CONST 0 (0)
18 COMPARE_OP 2 (==)
21 POP_JUMP_IF_FALSE 30
24 LOAD_CONST 1 (1)
27 JUMP_FORWARD 3 (to 33)
>> 30 LOAD_FAST 1 (1)
>> 33 LIST_APPEND 2
36 JUMP_ABSOLUTE 6
>> 39 RETURN_VALUE
所以基本上这两个实现执行类似的步骤。主要区别在于理解版本用CALL_FUNCTION替换了FOR_ITER。
由此我应该明白为什么列表理解更快,但我不知道。 所以我原来的问题仍然存在:
为什么列表理解更快?