背景

在日常工作中,经常会涉及到代码优化,直观来看,我们可以通过代码的运行速度、代码易读性等方面来判断我们代码优化是否有效,但对于一些比较苛刻的场景,就需要根据字节码来排查。这里介绍如何查看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

字节码解读

  1. 第一行 0 RESUME 0
  2. 第二行是加载 x 变量的值到栈顶,使用 LOAD_FAST 指令。接着,加载 y 变量的值到栈顶,也使用 LOAD_FAST 指令。然后,使用 BINARY_OP 指令进行加法操作,将 xy 的值相加,并将结果压入栈顶。最后,使用 STORE_FAST 指令将结果存储到变量 z
  3. 第三行是加载变量 z 的值到栈顶,使用 LOAD_FAST
  4. 最后一行使用 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

字节码解读

  1. 第一行 0 RESUME 0
  2. 第二行是加载局部变量 n 到栈顶,使用 LOAD_FAST 指令。然后加载常量 0 到栈顶,使用 LOAD_CONST 指令。接着使用 COMPARE_OP 指令比较栈顶的两个值是否相等,如果不相等,则跳转到标签 to 18;如果相等,则执行下一条指令。
  3. 接着是加载常量 1 到栈顶,使用 LOAD_CONST 指令,然后使用 RETURN_VALUE
  4. 标签 to 18
  5. 第五行是加载局部变量 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

如果条件为假则跳转或弹出栈顶元素