下面是四个具有相同输出的函数,但是它们要么是用列表理解编写的,要么是紧循环,还有一个对vs的内联条件的函数调用。
有趣的是,a
和{b
比{
此外,d
,它使用一个没有函数调用的紧循环,比使用列表理解和函数调用的a
快。
为什么函数a和b有相同的字节码,为什么b的性能比给定的相同字节码好得多?
import dis
def my_filter(n):
return n < 5
def a():
# list comprehension with function call
return [i for i in range(10) if my_filter(i)]
def b():
# list comprehension without function call
return [i for i in range(10) if i < 5]
def c():
# tight loop with function call
values = []
for i in range(10):
if my_filter(i):
values.append(i)
return values
def d():
# tight loop without function call
values = []
for i in range(10):
if i < 5:
values.append(i)
return values
assert a() == b() == c() == d()
import sys
>>> sys.version_info[:]
(3, 6, 5, 'final', 0)
# list comprehension with function call
>>> dis.dis(a)
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x00000211CBE8B300, file "<stdin>", line 2>)
2 LOAD_CONST 2 ('a.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 3 (10)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 RETURN_VALUE
# list comprehension without function call
>>> dis.dis(b)
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x00000211CBB64270, file "<stdin>", line 2>)
2 LOAD_CONST 2 ('b.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 3 (10)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 RETURN_VALUE
# a and b have the same byte code?
# Why doesn't a have a LOAD_GLOBAL (my_filter) and CALL_FUNCTION?
# c below has both of these
# tight loop with function call
>>> dis.dis(c)
2 0 BUILD_LIST 0
2 STORE_FAST 0 (values)
3 4 SETUP_LOOP 34 (to 40)
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 1 (10)
10 CALL_FUNCTION 1
12 GET_ITER
>> 14 FOR_ITER 22 (to 38)
16 STORE_FAST 1 (i)
4 18 LOAD_GLOBAL 1 (my_filter)
20 LOAD_FAST 1 (i)
22 CALL_FUNCTION 1
24 POP_JUMP_IF_FALSE 14
5 26 LOAD_FAST 0 (values)
28 LOAD_ATTR 2 (append)
30 LOAD_FAST 1 (i)
32 CALL_FUNCTION 1
34 POP_TOP
36 JUMP_ABSOLUTE 14
>> 38 POP_BLOCK
6 >> 40 LOAD_FAST 0 (values)
42 RETURN_VALUE
# tight loop without function call
>>> dis.dis(d)
2 0 BUILD_LIST 0
2 STORE_FAST 0 (values)
3 4 SETUP_LOOP 34 (to 40)
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 1 (10)
10 CALL_FUNCTION 1
12 GET_ITER
>> 14 FOR_ITER 22 (to 38)
16 STORE_FAST 1 (i)
4 18 LOAD_FAST 1 (i)
20 LOAD_CONST 2 (5)
22 COMPARE_OP 0 (<)
24 POP_JUMP_IF_FALSE 14
5 26 LOAD_FAST 0 (values)
28 LOAD_ATTR 1 (append)
30 LOAD_FAST 1 (i)
32 CALL_FUNCTION 1
34 POP_TOP
36 JUMP_ABSOLUTE 14
>> 38 POP_BLOCK
6 >> 40 LOAD_FAST 0 (values)
42 RETURN_VALUE
import timeit
>>> timeit.timeit(a) # list comprehension with my_filter
1.2435139456834463
>>> timeit.timeit(b) # list comprehension without my_filter
0.6717423789164627
>>> timeit.timeit(c) # no list comprehension with my_filter
1.326850592144865
>>> timeit.timeit(d) # no list comprehension no my_filter
0.7743895521070954
为什么a
和{b
有更好的字节码。值得注意的是,我认为a
需要一个LOAD_GLOBAL ? (my_filter)
和一个CALL FUNCTION
。例如,c
与a
相同,但没有列表理解,它使用这些字节码这些地址18和22。
然而,即使使用相同的字节码,b
也比a
好得多。这是怎么回事?
更有趣的是,d
使用了一个紧循环,但没有调用my_filter
,它比使用列表理解但调用my_filter
的b
快。看起来使用函数的开销超过了紧循环的开销。
我的目标是想弄清楚是否可以将列表理解的条件分解成一个函数,使列表理解更容易阅读。
列表理解被转换为内部函数,因为它们构建了一个单独的命名空间。
a
和b
中LC的内部函数不同:在这里您可以看到
a
中的函数调用和b
中的比较。在请注意,
a
和b
的字节码都只运行在别处定义的<listcomp>
对象。在由于包装器函数}是相同的,它们的字节码是相同的,只是listcomp的地址不同。在
a
和{在Python3.7中,dis模块还打印listcomp,下面是完整的代码和输出:
^{pr2}$在
对于Python<;3.7。见Python: analyze a list comprehension with dis
相关问题 更多 >
编程相关推荐