Python内核源码解析:OpCode字节码优化上

Python 内核源码中字节码的优化实现在文件Python/compile.c中,Python 的字节码优化是通过peephole优化器实现的。peephole优化器是一个简单的局部优化器,它对字节码进行扫描,找到一些可以优化的地方,然后进行优化。peephole优化器的实现在Python/peephole.c中。peephole叫窥孔优化,是一种局部优化,它只对一小段代码进行优化,而不是对整个程序进行优化。

compile.c 实现了 AST 到字节码的编译器,它的入口是PyAST_Compile函数。PyAST_Compile函数首先将 AST 转换为控制流图,然后对控制流图进行优化,最后将控制流图转换为字节码。

compile.c 中的优化实现入口是optimize_cfg函数,它对字节码的控制流图进行优化。控制流图是一个有向图,图中的节点是基本块,边是控制流。基本块是一段代码,它没有入口,只有一个出口。控制流图是一个有向无环图,它的入口是函数的入口基本块,出口是函数的出口基本块。控制流图的优化是通过对基本块进行优化来实现的。

优化实现入口

optimize_cfg函数是字节码的优化实现入口,它对字节码的控制流图进行优化。optimize_cfg函数的实现如下:

c 复制代码
static int
optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts)
{
    for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
        if (optimize_basic_block(c, b, consts)) {
            return -1;
        }
        clean_basic_block(b, -1);
        assert(b->b_predecessors == 0);
    }
    // ...
}

optimize_cfg函数首先对控制流图中的每个基本块进行优化,然后对基本块进行清理。optimize_basic_block函数是对基本块进行优化的函数,它的实现如下:

基本块优化

c 复制代码
/* Optimization */
static int
optimize_basic_block(struct compiler *c, basicblock *bb, PyObject *consts)
{
    assert(PyList_CheckExact(consts));
    struct instr nop;
    nop.i_opcode = NOP;
    struct instr *target;
    for (int i = 0; i < bb->b_iused; i++) {
        struct instr *inst = &bb->b_instr[i];
        int oparg = inst->i_oparg;
        int nextop = i+1 < bb->b_iused ? bb->b_instr[i+1].i_opcode : 0;
        if (is_jump(inst)) {
            /* Skip over empty basic blocks. */
            while (inst->i_target->b_iused == 0) {
                inst->i_target = inst->i_target->b_next;
            }
            target = &inst->i_target->b_instr[0];
        }
        else {
            target = &nop;
        }
        switch (inst->i_opcode) {
            /* Remove LOAD_CONST const; conditional jump */
            case LOAD_CONST:
            {
                PyObject* cnt;
                int is_true;
                int jump_if_true;
                switch(nextop) {
                    case POP_JUMP_IF_FALSE:
                    case POP_JUMP_IF_TRUE:
                        cnt = PyList_GET_ITEM(consts, oparg);
                        is_true = PyObject_IsTrue(cnt);
                        if (is_true == -1) {
                            goto error;
                        }
                        inst->i_opcode = NOP;
                        jump_if_true = nextop == POP_JUMP_IF_TRUE;
                        if (is_true == jump_if_true) {
                            bb->b_instr[i+1].i_opcode = JUMP_ABSOLUTE;
                            bb->b_nofallthrough = 1;
                        }
                        else {
                            bb->b_instr[i+1].i_opcode = NOP;
                        }
                        break;
                    case JUMP_IF_FALSE_OR_POP:
                    case JUMP_IF_TRUE_OR_POP:
                        cnt = PyList_GET_ITEM(consts, oparg);
                        is_true = PyObject_IsTrue(cnt);
                        if (is_true == -1) {
                            goto error;
                        }
                        jump_if_true = nextop == JUMP_IF_TRUE_OR_POP;
                        if (is_true == jump_if_true) {
                            bb->b_instr[i+1].i_opcode = JUMP_ABSOLUTE;
                            bb->b_nofallthrough = 1;
                        }
                        else {
                            inst->i_opcode = NOP;
                            bb->b_instr[i+1].i_opcode = NOP;
                        }
                        break;
                }
                break;
            }
            // ...
        }
    }
    return 0;
error:
    return -1;
}

当 opcode 为LOAD_CONST时,如果下一个 opcode 是POP_JUMP_IF_FALSEPOP_JUMP_IF_TRUE,则可以直接判断LOAD_CONST的值,然后根据判断结果直接跳转到目标位置。这样就可以减少一条指令的执行。switch 分支中还有其它的指令优化。

清理基本块

c 复制代码
static void
clean_basic_block(basicblock *bb, int prev_lineno) {
    /* Remove NOPs when legal to do so. */
    int dest = 0;
    for (int src = 0; src < bb->b_iused; src++) {
        int lineno = bb->b_instr[src].i_lineno;
        if (bb->b_instr[src].i_opcode == NOP) {
            /* Eliminate no-op if it doesn't have a line number */
            if (lineno < 0) {
                continue;
            }
            /* or, if the previous instruction had the same line number. */
            if (prev_lineno == lineno) {
                continue;
            }
            /* or, if the next instruction has same line number or no line number */
            if (src < bb->b_iused - 1) {
                int next_lineno = bb->b_instr[src+1].i_lineno;
                if (next_lineno < 0 || next_lineno == lineno) {
                    bb->b_instr[src+1].i_lineno = lineno;
                    continue;
                }
            }
            else {
                basicblock* next = bb->b_next;
                while (next && next->b_iused == 0) {
                    next = next->b_next;
                }
                /* or if last instruction in BB and next BB has same line number */
                if (next) {
                    if (lineno == next->b_instr[0].i_lineno) {
                        continue;
                    }
                }
            }

        }
        if (dest != src) {
            bb->b_instr[dest] = bb->b_instr[src];
        }
        dest++;
        prev_lineno = lineno;
    }
    assert(dest <= bb->b_iused);
    bb->b_iused = dest;
}

clean_basic_block函数用于清理基本块,它的作用是删除一些不必要的指令。比如,删除一些没有意义的NOP指令。

总结

Python 的字节码优化是通过peephole优化器实现的,它是一个简单的局部优化器,对字节码进行扫描,找到一些可以优化的地方,然后进行优化。

相关推荐
许彰午4 分钟前
72_Python爬虫基础BeautifulSoup
爬虫·python·beautifulsoup
zhanghongyi_cpp37 分钟前
10. 实验书3.4.2 筛选达到预警阈值的病虫害数据
python
tuddy7894641 小时前
Codex++ 安全边界探秘:从模型能力到风险防御
人工智能·python·安全
C++、Java和Python的菜鸟1 小时前
第1章 集合高级
java·jvm·python
梦帮科技1 小时前
UE5 GAS 实战:用 Gameplay Ability System 搭建「赛博修真」境界与技能体系
c++·人工智能·python·ue5·c#
码来的小朋友2 小时前
手把手教你用 Python + PyQt5 做一个可视化图片切图工具
开发语言·python·microsoft
weixin199701080162 小时前
[特殊字符]《京东订单API(jd.order.detail.get)对接ERP:企业认证+OAuth授权避坑指南》(附Python源码)
java·数据库·python
云烟成雨TD3 小时前
LangFlow 1.x 系列【3】入门案例
人工智能·python·agent
创世宇图3 小时前
【Python工程化实战】Python 服务的结构化日志体系:structlog + JSON 输出 + 日志分级策略
python·elk·structlog·结构化日志·可观测性
aaaameliaaa3 小时前
计算斐波那契数(递归、迭代)(1,1,2,3,5.....)
c语言·开发语言·笔记·算法·排序算法