python - 为什么这两个函数在dis.dis下反汇编时字节码相同?
问题描述
接下来是四个具有相同输出的函数,但要么使用列表推导式编写,要么使用紧密循环编写,以及对内联条件的函数调用。
有趣的是,a
在b
反汇编时具有相同的字节码,但是b
比a
.
此外,d
它使用没有函数调用的紧密循环,比a
使用带有函数调用的列表推导更快。
为什么函数 a 和 b 具有相同的字节码,为什么 b 的性能比给定相同字节码的 a 好得多?
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
具有相同的字节码?我本来希望b
有更好看的字节码。值得注意的是,我会认为这a
需要 aLOAD_GLOBAL ? (my_filter)
和 a CALL FUNCTION
。例如,与列表解析c
相同a
但没有列表解析,它在地址 18 和 22 上使用这些字节码。
但是,即使使用相同的字节码,b
其性能也比a
. 这里发生了什么?
更有趣的是d
,它使用紧密循环但没有调用 to my_filter
,比b
使用列表推导式但调用 to更快my_filter
。看起来使用函数的开销超过了紧密循环的开销。
我的目标是尝试弄清楚我是否可以将列表理解的条件分解为一个函数,以使列表理解更易于阅读。
解决方案
请注意,两个字节码a
都只b
运行<listcomp>
在别处定义的对象。
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x00000211CBE8B300, file "<stdin>", line 2>)
由于 wrapper 函数a
和b
是相同的,它们的字节码是相同的,只是 listcomps 的地址不同。
在 python 3.7 中, dis 模块还打印 listcomps,这是完整的代码和输出:
import sys
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]
print(sys.version)
print('-' * 70)
dis.dis(a)
print('-' * 70)
dis.dis(b)
--
3.7.3 (default, May 19 2019, 21:16:26)
[Clang 10.0.1 (clang-1001.0.46.4)]
----------------------------------------------------------------------
9 0 LOAD_CONST 1 (<code object <listcomp> at 0x1065c61e0, file "/w/test/x.py", line 9>)
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
Disassembly of <code object <listcomp> at 0x1065c61e0, file "/w/test/x.py", line 9>:
9 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 16 (to 22)
6 STORE_FAST 1 (i)
8 LOAD_GLOBAL 0 (my_filter)
10 LOAD_FAST 1 (i)
12 CALL_FUNCTION 1
14 POP_JUMP_IF_FALSE 4
16 LOAD_FAST 1 (i)
18 LIST_APPEND 2
20 JUMP_ABSOLUTE 4
>> 22 RETURN_VALUE
----------------------------------------------------------------------
13 0 LOAD_CONST 1 (<code object <listcomp> at 0x1066188a0, file "/w/test/x.py", line 13>)
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
Disassembly of <code object <listcomp> at 0x1066188a0, file "/w/test/x.py", line 13>:
13 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 16 (to 22)
6 STORE_FAST 1 (i)
8 LOAD_FAST 1 (i)
10 LOAD_CONST 0 (5)
12 COMPARE_OP 0 (<)
14 POP_JUMP_IF_FALSE 4
16 LOAD_FAST 1 (i)
18 LIST_APPEND 2
20 JUMP_ABSOLUTE 4
>> 22 RETURN_VALUE
对于蟒蛇 < 3.7。请参阅Python:使用 dis 分析列表推导
推荐阅读
- javascript - JavaScript 本地存储在 Chrome 和 Edge 中被删除,但在 Firefox 中没有
- ember-cli - Ember:未捕获的错误:找不到模块“apollo-cache”
- javascript - 动态使用 React Flow
- docker-compose - 如何将现有的简单机器论坛 (SMF) 安装移动到 docker-compose?
- loops - 在python中迭代字典的键和值
- r - 在 R 中加载 lmSupport 包的问题
- arrays - MATLAB - repmat 值到单元格数组中,其中单个单元格元素的大小不相等
- python - 如何绘制具有多个时间标记词干的时间线?
- git - Egit不断抛出“拒绝-非快进”
- construct-3 - 构造 3:部署后显示游戏加载屏幕,然后该屏幕为空白{在控制台中发生此错误}