Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

优化为什么必须保留行为

复习

  • 数据流分析:为优化提供“情报”
  • 静态单赋值形式:让分析和优化更简单
  • 中间表示:优化的载体

TL;DR

  • 优化可以改写程序,但绝不能改变可观察的行为
  • 这是优化的第一原则
  • 常见被禁止的改动:改变结果、改变副作用顺序等
  • 只有在“等价”的写法里挑更快的,才是安全的优化

正文

  终于来到优化了。前面讲了那么多分析,就是为了这一刻:让程序跑得更快。

  但在动手之前,必须先立下一条规矩,而且它是第一原则——优化可以改写法,但绝不能改变程序的行为

什么叫“行为不变”

  程序的行为,指的是它对外可观察的一切

  • 最终的输出结果
  • 副作用的产生与顺序(比如打印、写文件、修改共享状态)
  • 是否抛出异常、在哪一步抛出

  一行代码哪怕“看起来毫无用处”,只要它有副作用,就不能随便删;一段计算哪怕“看起来可以重排”,只要改变了副作用顺序,就可能出事。优化器手里是一把刀,但只能切那些切了不影响的肉。

一个反例

  举个常见的坑:浮点数加法不满足结合律。

  数学上 (a + b) + c 等于 a + (b + c),于是优化器可能想把它们重排以方便计算。但在浮点运算里,由于精度舍入,两种顺序的结果可能不同。如果语言规定必须按原顺序算,那么这种“重排”就会改变行为,是不允许的。

  类似地,把一个“结果没人用”的函数调用删掉,如果这个调用内部有副作用(比如打印了一行字),删了就会改变行为。

先想清楚“等价”,再想“更快”

  所以,优化的正确姿势是:

  1. 先判断两种写法是否等价(对所有合法输入,可观察行为都相同)
  2. 只有在等价的前提下,才选出更快的那种

  “更快”永远排在“行为一致”之后。 一个变快却算错的程序,比一个慢但正确的程序糟糕得多——正确是底线,性能是加分项。

  有了这条底线,接下来的各种优化,就可以放心地一条条来看了。

思考题 1

  为什么说“优化可以改写法,但不能改行为”?

思考题 2

  举一个“看起来能优化、其实会改变行为”的例子。

小结

知识点

  • 行为指可观察的结果、副作用与异常
  • 优化必须保持行为不变
  • 有副作用的代码不能随意删除或重排
  • 先判定等价,再选择更快的写法

参考资料

  1. Wikipedia(zh):编译器优化:在保持语义的前提下改进程序
  2. Wikipedia(zh):副作用 (计算机科学):函数对外部状态的改变

思考题答案(仅供参考)

思考题 1

  因为优化只是让程序更高效,而不是改变它的含义。程序的输出、副作用和异常等可观察行为,必须与优化前一致,否则优化后的程序就不再是原来那个程序了,用户会得到错误结果。

思考题 2

  例如把“结果没人用”的打印函数调用删掉——它看似没产出,却有输出这个副作用;再如重排浮点加减的顺序,可能因舍入导致结果变化。这些都“看起来能优化”,实则改变了行为。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪