背景
在日常工作中,经常会涉及到代码优化,直观来看,我们可以通过代码的运行速度、代码易读性等方面来判断我们代码优化是否有效,但对于一些比较苛刻的场景,就需要根据字节码来排查。这里介绍如何查看python字节码以及如何分析字节码的方法。
示例1
方法构造
def func(x, y):
z = x + y
return z
查看字节码
import dis
dis.dis(func)
得到如下结果
1 0 RESUME 0
2 2 LOAD_FAST 0 (x)
4 LOAD_FAST 1 (y)
6 BINARY_OP 0 (+)
10 STORE_FAST 2 (z)
3 12 LOAD_FAST 2 (z)
14 RETURN_VALUE
字节码解读
- 第一行
0 RESUME 0
- 第二行是加载
x
变量的值到栈顶,使用LOAD_FAST
指令。接着,加载y
变量的值到栈顶,也使用LOAD_FAST
指令。然后,使用BINARY_OP
指令进行加法操作,将x
和y
的值相加,并将结果压入栈顶。最后,使用STORE_FAST
指令将结果存储到变量z
- 第三行是加载变量
z
的值到栈顶,使用LOAD_FAST
- 最后一行使用
RETURN_VALUE
示例2
方法代码
该方法展示一个简单的递归求阶乘的方法
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)
字节码查看
dis.dis(factorial)
得到如下结果
1 0 RESUME 0
2 2 LOAD_FAST 0 (n)
4 LOAD_CONST 1 (0)
6 COMPARE_OP 2 (==)
12 POP_JUMP_FORWARD_IF_FALSE 2 (to 18)
3 14 LOAD_CONST 2 (1)
16 RETURN_VALUE
5 >> 18 LOAD_FAST 0 (n)
20 LOAD_GLOBAL 1 (NULL + factorial)
32 LOAD_FAST 0 (n)
34 LOAD_CONST 2 (1)
36 BINARY_OP 10 (-)
40 PRECALL 1
44 CALL 1
54 BINARY_OP 5 (*)
58 RETURN_VALUE
字节码解读
- 第一行
0 RESUME 0
- 第二行是加载局部变量
n
到栈顶,使用LOAD_FAST
指令。然后加载常量0
到栈顶,使用LOAD_CONST
指令。接着使用COMPARE_OP
指令比较栈顶的两个值是否相等,如果不相等,则跳转到标签to 18
;如果相等,则执行下一条指令。 - 接着是加载常量
1
到栈顶,使用LOAD_CONST
指令,然后使用RETURN_VALUE
- 标签
to 18
- 第五行是加载局部变量
n
到栈顶,使用LOAD_FAST
指令。然后加载全局变量NULL + factorial
到栈顶,使用LOAD_GLOBAL
指令。接着加载局部变量n
到栈顶,再加载常量1
到栈顶,使用BINARY_OP
指令计算n - 1
。然后使用PRECALL
指令指示接下来的CALL
指令调用栈顶的函数。接着使用BINARY_OP
指令将函数返回值与n
相乘,最后使用RETURN_VALUE
常见字节码指令
指令 | 描述 |
LOAD_CONST | 加载常量到栈顶 |
LOAD_FAST | 加载局部变量到栈顶 |
LOAD_GLOBAL | 加载全局变量到栈顶 |
LOAD_ATTR | 加载对象属性到栈顶 |
STORE_FAST | 存储值到局部变量 |
STORE_GLOBAL | 存储值到全局变量 |
STORE_ATTR | 存储值到对象属性 |
BINARY_ADD | 执行二元加法操作 |
BINARY_SUBTRACT | 执行二元减法操作 |
BINARY_MULTIPLY | 执行二元乘法操作 |
UNARY_NEGATIVE | 执行一元取负操作 |
UNARY_NOT | 执行一元逻辑取反操作 |
COMPARE_OP | 比较操作 |
POP_TOP | 弹出栈顶元素 |
ROT_TWO | 交换栈顶的两个元素的位置 |
ROT_THREE | 旋转栈顶的三个元素的位置 |
ROT_FOUR | 旋转栈顶的四个元素的位置 |
CALL_FUNCTION | 调用函数 |
RETURN_VALUE | 返回函数值 |
JUMP_FORWARD | 向前跳转 |
JUMP_ABSOLUTE | 绝对跳转 |
JUMP_IF_FALSE_OR_POP | 如果条件为假则跳转或弹出栈顶元素 |