最難調(diào)試修復的 bug 是怎樣的?
作者:doodlewind
鏈接:https://www.zhihu.com/question/21991014/answer/1513267624
真正最難修復的 bug,其解決靠的已經(jīng)不是個人英雄主義的單打獨斗,而是全世界頂尖高手集體智慧的「飽和式搶救」了。
這種 bug 的解決,甚至能直接使其解決者自此一戰(zhàn)而揚名天下。
1994 年著名的 Intel CPU 浮點運算 bug,就是這樣的傳奇 bug。
緣起
當時,Intel 為奔騰 CPU 的浮點除法指令 FDIV 加入了一種新型的實現(xiàn)。這是 Sweeney-Robertson-Tocher(SRT)算法的一種高性能變體,依賴了一個共有 2048 項的硬件查找表。因為這種算法只會訪問整個 128x16 尺寸查找表中的一個梯形子集,所以這 2048 項中只有略多于一半的項會被用到。由于一些意外,這 1066 項中有 5 項的值被錯誤地設置為 0(而不是正確的 2),因此可能導致運算結果的錯誤。但是,這些錯誤的索引只會在極少數(shù)情況下被訪問到,以至于這個問題沒有被 Intel 研發(fā)流程中的隨機測試所發(fā)現(xiàn)。更可怕的是,在除法算法的前 8 個執(zhí)行步驟中,錯誤的這幾項還永遠不會被訪問到,因此錯誤結果與真實結果之間僅有輕微的差異——這種差異對于高精度計算來說可能非常關鍵,但普通場景下幾乎不可能發(fā)現(xiàn)(據(jù)稱概率是每 90 億次運算出現(xiàn)一次,相當于七百年一遇)。
這是人們事后從上帝視角給出的復盤。假如你根本不知道硬件電路中埋著這樣的一個雷,你覺得寫應用層業(yè)務遇到問題時該從何下手呢?
察覺
這個 bug 雖然非常隱蔽,但卻沒有躲過美國林奇堡學院 Thomas Nicely 教授善于察覺要素的眼睛。他在多臺計算機上運行同樣的算法來對孿生質數(shù)的商進行求和時,發(fā)現(xiàn)計算結果在不同機器之間存在差異。
Nicely 花了幾個月的時間(注意時間單位是月)來檢查可能的差異原因,最終認為問題來自于使用了奔騰 CPU 的系統(tǒng)。發(fā)現(xiàn)問題之后,他在 1994 年的 10 月 24 日(程序員節(jié))向 Intel 提出了反饋,并于 10 月 30 日向其他的一些聯(lián)系人發(fā)送了報告問題的電子郵件,其中有一名收件人將其內(nèi)容轉發(fā)到了 CompuServe 網(wǎng)絡上。電子工程時報的記者 Alex Wolfe 發(fā)現(xiàn)了這個帖子,并將其轉發(fā)給了挪威工程師 Terje Mathisen。在收到消息后的幾個小時內(nèi),Mathisen 就成功復現(xiàn)了 Nicely 教授的例子。他用匯編語言寫了一個簡單的測試用例,于 11 月 3 日在新聞組 comp.sys.intel 內(nèi)發(fā)布了一系列關于 FDIV 指令錯誤的帖子。一天后,德國的 Andreas Kaiser 找到了 20 多個特殊的數(shù)字,這些數(shù)字的倒數(shù)在奔騰 CPU 上的計算精度只達到了單精度(也就是 32 位 float 的水平,精確到小數(shù)點后 7 位)。
定位
與此同時,加州 Vitesse 半導體公司的 FPU(浮點單元)設計師 Tim Coe 從 Kaiser 給出的列表中找到了線索,分析推斷出了 Intel 的 FPU 設計師們是如何設計除法電路的。他正確地推測,奔騰 CPU 的除法指令采用了基數(shù)為 4 的 SRT 算法,每個時鐘周期會產(chǎn)生兩個 bit 的商。這樣可以讓奔騰 CPU 的除法速度達到過去相同時鐘速率下 Intel 芯片的兩倍。
Coe 創(chuàng)建了一個模型,以此解釋了 Kaiser 所報告的誤差。他還發(fā)現(xiàn),如果對于分子不為 1 的除法運算,這還可能帶來更大的相對誤差。基于這個模型,他找到了一對七位整數(shù),它們的商 4195835/3145727 可能是最壞情況下的錯誤實例。Coe 于 11 月 14 日將這個例子發(fā)布到了 comp.sys.intel 新聞組上。
在 Coe 發(fā)帖前幾天,美國麻省一家公司的老板 Cleve Moler 從另一個渠道得知了 FDIV 的錯誤。Moler 起初只是對此感到好奇,并未實際參與。但 Coe 發(fā)布的問題定位,使 Moler 的興趣大大提升。11 月 15 日,Moler 在新聞組上發(fā)帖,總結了截至當時 Nicely 和 Coe 各自案例中的已知情況,并找到了這兩種情況下的 bug 規(guī)律,即除數(shù)都略少于 3 乘以 2 的某個整數(shù)次冪——這個 Moler 可不是土老板,他當過斯坦福的數(shù)學教授。
舉個例子,2^20 = 1048576,而上面的除數(shù) 3145727 除以 3,則是 1048575.666666……像不像是給數(shù)學家玩的密室逃脫游戲?
輿情
11 月 7 日,參與反饋 bug 的 Wolfe 在電子工程時報上報道了此事,關于奔騰 CPU 這個 bug 的消息,迅速在互聯(lián)網(wǎng)上流傳了開來。
11 月 22 日,美國 NASA 噴氣推進實驗室(JPL)的兩位工程師向采購部門提出建議,認為實驗室應該停止訂購使用奔騰芯片的計算機。CNN 的記者聽說了 JPL 的決定,于是找到 Moler 進行了采訪。當天晚上,CNN 在電視節(jié)目上報道了奔騰 CPU 的這個 bug,將事態(tài)升級為了國民級新聞。到了兩天之后的感恩節(jié),包括紐約時報和波士頓環(huán)球報在內(nèi)的主流媒體都對此進行了報道。在接下來的幾周內(nèi),更是出現(xiàn)了數(shù)百篇關于此事的文章。
注意這時整個社會都在 BB,但這個 bug 仍然沒有解決。
拆彈
基于自己找到的規(guī)律,Moler 開始與 Coe、Mathisen、Peter Tang(來自美國阿貢國家實驗室),以及 Intel 的幾位軟硬件工程師合作,嘗試解決這個 FDIV 錯誤。只要解決了這個 bug,還能一并解決掉奔騰 CPU 上由此產(chǎn)生的片上正切、正交和求余指令的衍生 bug。到 12 月 5 日,他們開發(fā)出了一種巧妙的修復方法:檢查除數(shù)有效位部分的的高四位(浮點數(shù)有效位部分即 fraction,如下圖示例中的紅色部分),如果它們是 0001、0100、0111、1010 或 1101,那么就在執(zhí)行除法運算之前,將除數(shù)和被除數(shù)都同乘以 15/16。在這五種情況下,這種乘以 15/16 的效果都能使除數(shù)從「危險」狀態(tài)轉入「安全」狀態(tài)。這時可以保證縮放后操作數(shù)的商,始終能與原始操作數(shù)的正確商相同。幾天后他們進一步優(yōu)化了算法,只有當除數(shù)有效位的八個高位是 00011111、01001111、01111111、10101111 或 11011111 時,才將操作數(shù)按 15/16 縮放,從而大大減少了額外的運算。這項優(yōu)化技術被公布到了新聞組,可供全社會無償自由使用。

32 位單精度浮點數(shù)結構,后 23 位為有效位
于是,報道「該公司修復了 Intel 奔騰 CPU 浮點數(shù) bug」的新聞,迅速登上了包括紐約時報在內(nèi)的各大主流媒體。
這家當時還名不見經(jīng)傳的小公司,由此正式出現(xiàn)在了公眾視野之中。
總結
這個 FDIV bug 事件,實在有眾多傳奇之處:
極其隱蔽的 bug 來源
極長的定位時間
世界各地高手(數(shù)學家與軟硬件工程師)跨領域的接力式努力
堪稱奇技淫巧的黑魔法 fix
轟動性的媒體傳播效應
這簡直是個教科書級的程序員題材電影劇本啊……
只剩下最后一個問題了,解決 bug 的這家公司到底是什么呢?
你可能早已經(jīng)看出來了,它就是出品 MATLAB 的 MathWorks。
對,就是那個中國限購的 MATLAB——很多年以后,它將以另一種形式再次出現(xiàn)在中國公眾的茶余飯后談資中,但那就是另一個話題了。
你看,我們天天講的自主研發(fā),可真不止是件喊口號的事情呀。
后記
Intel 因為這個 FDIV bug 事件虧了近 5 億美元,但 MATLAB 則成為了此事的最大贏家。1994 年的 MathWorks 只是個 200 多人規(guī)模的小公司,而今天它已經(jīng)是有超過 5000 名員工的世界巨頭了。
在 MathWorks 成立近 40 年后,當年親自下一線修 bug 的美國工程院院士 Moler,又自己動手為 MATLAB 語言撰寫了歷史研究文獻《A History of MATLAB》。在這份去掉參考文獻約 40 多頁的資料中,對這個 FDIV bug 的介紹橫跨了整整 3 頁。這個 bug 的難度與歷史地位,由此可見一斑。
《A History of MATLAB》的 4.5 節(jié)是本文的主要內(nèi)容來源。

