因为它不是。对不关键部分的过早优化才是。Donald Knuth 这句话的原文是:

Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

https://wiki.c2.com/?PrematureOptimization

翻译过来是(注意前半句话):

程序员们浪费了大量的时间去思考、担心程序中非关键部分的性能,如果考虑整体代码 的 debug 和维护的时间,则这些性能优化的开销是得不偿失的。对于约 97% 的小优化 点,我们应该忽略它们:过早优化为什么万恶之源。而对于剩下的关键的 3%,我 们则不能放弃优化的机会。

原文说的是:对于不关键的部分不要过早优化。而原因主要是性价比太低。

什么部分才关键?

一看预算、二看数量、三看单价。

  • “预算”是指你能容忍的时间/空间。支付宝的一笔转帐大概率需要在 200ms 内完成;一笔贷款申请 10 min 审批可能算快的;淘宝新商品也许上线 1h 后才能搜到。
  • “数量”是指某个操作被调用的次数。量变引起质变。如一般 64 位的 String 里 length 字段是 8 个字节,100 万个就被放大到 8MB 内存了。
  • “单价”就是某个操作本身的耗时了,通常需要和数量结合起来看。

换句话说,当单价*数量有可能大于预算时,要么优化数量,要么优化单价。

问题在于,我怎么知道哪部分操作可能出问题?这也许也是 Donald Knuth 最想要表达的:我们并不知道,所以要最后通过 profile 找到。借用知乎网友的回答1,原文应该翻译为“盲目优化是万恶之源”。

也许上面的说法都指向“不要过早优化”,但实际编码中,多数情况下,我认为需要尽早优化。原因有二:

  1. 事后优化代价很大
  2. 事实上通过经验是可以预判优化点的

事后优化头很大

这里我举两个亲身经历的事件。

第一个是做规则引擎,允许用户编写规则,QA 发现在导入一个大的配置文件 (2k个规则) 需要 2 小时,在 profile 之后也很快定位到瓶颈:读写数据库的次数过多。之前的逻辑是针对界面创建规则写的,在创建规则过程中需要访问多次数据库,而批量导入时重用了单次的规则,没有合并批量的读写,导致特别慢。

思路很清晰,批量读写数据库。问题在于写入规则的逻辑很复杂,费了九牛二虎之力才理清楚逻辑改成了批量的版本,结果 bug 还是修了好几个迭代。

第二个是有一个微服务,调用的协议使用了 Avro,请求和响应中会携带数据的 Schema。经过 profile 后发现数据的序列化和反序列化占用了最多的时间,而 Schema 的内容远大于数据本身。

由于有些服务已经上线,修改协议的难度比较大,后期只能尝试增加批量的接口一定程度上缓解。

除此之外,还可能遇到“处处是热点,处处是瓶颈”的情况。比如 Rust 的编译时间很长,可是 profile 了编译器发现没有明显的热点,每个过程都占用不多,但整体性能就是优化不了。

让优化刻入骨髓

虽然提前判断出优化点很大程度上依赖于经验,但其实还是有一定“套路”的。

比如考虑“数量”,只要一个操作的次数与输入的规模相关,且输入规模可能爆炸,就要注意去优化。比如上节示例里的批量数据库读写。

比如考虑“单价”,比如上例中,Schema 内容大于数据内容,本身就是不合理的,单价的损耗太大了,就需要在设计时提前考虑。当然“单价”一般要依托于数量。

对于“处处是热点,处处是瓶颈”其实就没有特别好的方法了,只能说,要把一些知识化成习惯。例如知道了二维数组先遍历行再遍历列能更高效利用缓存,把它作为编码习惯就好了。

当然,最最重要的还是确定“预算”!接需求的时候确认输入的规模,能接受的延时等等都能让我们更好地理解优化的边界和粒度。

小结

不要让“过早优化是万恶之源”成为懒惰的借口。过早优化不好的前提是你不知道瓶颈在哪,而在实际中是有迹可循的:预算、数量、单价。事后的优化往往代价会很大,预防才是最便宜的治疗。

参考