软件设计

过早优化是万恶之源:Knuth 真正想表达的意思

Knuth 所说的“过早优化是万恶之源”其实是被截断的引文。本文整理原文中“忘掉97%、专注于3%”的语境,以及先用性能分析器确认瓶颈、再进行优化的实践准则。

4 分钟阅读
过早优化是万恶之源:Knuth 真正想表达的意思 封面图

在代码审查中,你应该至少听过一次“过早优化是万恶之源”这句话。

但找到原文读过之后,事情就有些不同了。Donald Knuth 绝不是在说“不要优化”。

先说结论:Knuth 说的是“忘掉97%的琐碎优化,但绝不要错过真正重要的3%”。常被引用的半句话把后半段完整地删掉了。

今天我们来整理这句话的真实语境,以及代码究竟应该在什么时候优化。

真正的原文是这样

这句话出自 Donald Knuth 于1974年撰写的一篇论文,标题是“Structured Programming with go to Statements”,发表在 ACM Computing Surveys 上。

原文如下。

“我们应该忘掉琐碎的效率,也就是说,大约97%的时间都应如此。过早优化是万恶之源。但在那关键的3%中,我们绝不能错过机会。”

看到了吗?我们熟悉的句子其实只截取了中间一段。

前面是“忘掉97%”,后面接着“不要错过3%”。这三句话是一个整体。

Knuth 不是说不要优化,而是说要选择把精力投入到哪里。

桌上放着 Structured Programming 论文打印件,句子被荧光笔标出
读过原文后我才发现,被删掉的后半句才是真正的核心

“忘掉97%,专注于3%”这才是核心

Knuth 在同一篇论文中还指出了另一点。

程序员为了担心程序中并不重要部分的速度,浪费了大量时间。

这种提前进行的优化反而会让调试和维护变得更加困难。

也就是说,问题不在于优化本身,而在于时机和位置。在还不知道瓶颈在哪里时就随意修改,才是祸根。

不是不要优化,而是不要毫无依据地到处优化。


那么究竟什么时候应该优化?

这是最常见的问题。答案很简单:测量之后再做。

找到 Knuth 所说的关键3%,靠的不是感觉,而是测量。运行性能分析器,找出实际消耗时间的地方,然后处理那里。

使用 Swift 时,可以先这样确认瓶颈。

// 测量实际是哪些代码消耗了时间
let clock = ContinuousClock()
let elapsed = clock.measure {
    slowFunction()
}
print(elapsed)  // 耗时较长的部分就是那个 '3%'
开发者笔记本屏幕显示性能分析器输出,缓慢函数被突出显示
不要凭感觉判断,先运行性能分析器,瓶颈就会清晰可见。

查看测量结果后,我们通常会发现自己的预期错了。以为会慢的地方其实没问题,反而是一个没想到的循环拖慢了整体。

这样区分过早优化和合理优化会更容易理解。

分类 过早优化 合理优化
时机 测量前,凭感觉 测量后,依据数据
对象 任何显眼的地方 确认是瓶颈的3%
结果 代码只是变得更复杂 实际性能提升

还有一点:这句话可能根本不是 Knuth 说的

这里还有一段有趣的后续故事。

这句话也常被认为出自 Tony Hoare(C. A. R. Hoare)之口。许多常用的名言网站也把它列为 Hoare 的名言。

但 Knuth 本人在1989年的文章“The Errors of TeX”中,称它为“Hoare’s Dictum”。

而 Hoare 又说,这不是他的话,而是 Knuth 的话。

两位大师互相把出处推给对方;不过根据文献证据,主流观点认为这是 Knuth 的表述,首次出现在1974年的论文中。

无论最先是谁说的,重要的是它原本的含义被截成了一半并遭到误解。


常见问题

Q. 那是不是从一开始就不用在意性能?

不是。像算法选择这样决定结构的判断,从一开始就要考虑。Knuth 让我们忘掉的是“琐碎的”效率,而不是设计层面的决策。

Q. 什么时候测量?

先创建能运行的代码。先让它跑起来;如果速度慢,再用性能分析器找出瓶颈,只修复那个位置。


下次再遇到这句话时,也请想起后半句:忘掉97%,但不要错过3%。

警惕未经测量就进行优化,但面对真正的瓶颈也不要退缩。这就是 Knuth 真正想表达的意思。

如果今天有哪段代码因为“感觉很慢”而想修改,建议先运行一次性能分析器。

参考资料