軟體設計

過早最佳化是萬惡之源: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 真正想說的話。

如果今天有哪段程式碼因為「看起來好像很慢」而想修改,建議先執行一次分析器。

參考資料