Skip to content

为什么选择窥孔优化?折叠常量,去掉无用的代码对编译之后的程序执行有什么影响?

窥孔优化是一种局部优化技术。在局部作用域下,我们知道某个变量是否被更新,或者被引用,如果一个变量值被调用一次,完全可以减少加载该元素的次数。常量折叠之所以存在,是因为某个常量在程序生命周期内不会改变,在被其它的表达式调用时,可以直接进行数值计算,将最终的值 PUSH 回栈中。去掉无用的字节码,减少字节码的出栈入栈次数。控制流优化将多个指令优化为一个或者少量的指令,比如表达式 if not 可以这么做,将 UNARY_NOT 和 POP_JUMP_IF_FALSE 替换成一个指令 POP_JUMP_IF_TRUE。

别小看这些“微不足道”的优化。试着想一想,如果程序被执行了一千次,一万次,节约的程序执行耗时,聚少成多,这是一个长期效益产出的技术。

优化不止于节省时间,也能节约服务器的内存开销,减少 CPU 指令的执行次数,将更多的性能留给其它的程序。

窥孔优化得到了编译原理的理论支持,也在各种大型编程语言中得到了实践。这种技术在 Python 内核中应用实践也是必然结果。

字节码优化相关逻辑在文件 Python/compile.c 中可以找到。阅读相关代码枯燥乏味,结合抽象语法树的结构打印 ast.dump(ast.parse(code, 'exec'), indent=4),编译之后的字节码输出,对比相关内核源码,从这个思路,理解作者的实现逻辑及意图更加方便。

阅读大型项目的源码不要畏惧,下载 Python 内核源码,在 IDE 中打开,核心函数一个一个的分析,每行代码写上自己理解的注释,反复阅读,修改自己的理解。

像这个函数 static PyCodeObject * assemble(struct compiler *c, int addNone),其作用是生成字节码。在这个函数体内,有些比较重要的函数调用,consts = consts_dict_keys_inorder(c->u->u_consts); 其作用是为了生成常量,if (optimize_cfg(c, &a, consts)) { ... } 实现控制流优化, if (trim_unused_consts(c, &a, consts)) { ... } 移除未使用的常量(试着想想没有使用的字节码生成来了做什么)?还有循环调用assemble_emit(&a, &b->b_instr[j]) 生成字节码。

OpCode 优化不是三两篇文章就可以写清楚的,我将持续更新自己更深层次的理解。