优化流水线总装
复习
- 常量折叠与常量传播:把常量算掉、传开
- 无用代码删除:去掉白算或走不到的代码
- 循环优化与函数内联:减少重复工作,并为其它优化创造机会
TL;DR
- 优化不是一步完成,而是一条“流水线”
- 多个优化依次进行,还会相互配合、互相引出机会
- 一轮常常不够,要反复几轮直到稳定
- 每一步都必须保持行为不变
正文
这一章是优化部分的收尾。前几章分别讲了常量折叠、无用代码删除、公共子表达式消除、循环优化、内联、跨过程分析……它们不是各自独立的,而是串成一条流水线,协同工作。
优化会“互相喂饭”
单个优化已经有用,但它们真正的威力,来自彼此配合:
- 常量传播把变量变成常量后,常量折叠又能算掉更多表达式
- 折叠之后,一些条件变成恒真或恒假,无用代码删除就能砍掉整段分支
- 删掉分支后,控制流变简单,公共子表达式消除和循环优化又有了新空间
你看,一个优化制造出的新局面,常常正是另一个优化想要的。 所以,编译器会把这些优化按一定顺序排好,让它们一个接一个地跑。
一轮不够,要反复跑
更麻烦的是:跑一轮往往不够。
因为某种优化可能为前面已经跑过的优化,制造出新的机会。比如常量传播在最后又暴露出一个可折叠的表达式,那就得再折一次。于是,编译器的做法是反复运行这条流水线,直到程序不再发生变化(达到一个“不动点”)。
这和前面数据流分析“迭代到稳定”,是同一个思路:只要还能改进,就继续,直到改不动为止。 当然,为了不让编译时间失控,实际编译器也会设一个轮数上限。
快,也是要权衡的
最后提醒一件事:优化并不是越多越好、级别越高越快。
- 优化会让编译变慢:分析、改写都要花时间 - 越激进的优化,收益越不确定,有时甚至因代码膨胀而变慢 - 所以编译器提供不同优化级别:开发时用低级别图快,发布时用高级别图快(运行快)
又是取舍。 编译器的“快”,分两种:编译过程本身要快,还是生成出来的程序要快。两者常常此消彼长。
至此,“把程序改快”这一阶段就完成了。接下来,我们要把优化好的中间表示,真正落到机器指令上——这就是编译器的后端。
思考题 1
为什么优化通常要“反复几轮”,而不是一遍过?
思考题 2
优化级别越高,生成出来的程序一定越快吗?
小结
知识点
- 多个优化串成流水线,彼此配合
- 一种优化常为另一种制造新机会
- 需反复运行直到程序不再变化
- 优化级别是编译时间与运行性能的权衡
参考资料
- Wikipedia(zh):编译器优化:优化流水线与级别
- Wikipedia(zh):优化编译器:执行多种优化的编译器
思考题答案(仅供参考)
思考题 1
因为某种优化常常为已经跑过的优化制造出新的机会,例如常量传播在后续又暴露出可折叠的表达式。若只跑一遍就会漏掉这些新机会,所以要反复运行,直到程序不再变化为止。
思考题 2
不一定。更高的优化级别通常让运行更快,但可能使编译时间显著增加,而且越激进的优化收益越不确定,有时还会因代码体积膨胀、缓存不友好而变慢。所以“更高级别”不等于“一定更快”,仍需权衡。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪