第107章 三十二個證人

投票推薦 加入書籤 小說報錯

  江臨原來的計劃是,是把這些長遠問題全部封存,留待即將到來的廢土時間去解決。

  但是回到家裡,夜深人靜,整個人一空閒下來,就還是忍不住把陳啟明給的壓縮包拖進電腦的工作目錄里。

  microkernel_rank_sort_baseline.zip。

  右鍵,解壓。

  文件夾在屏幕的樹狀圖里一層層鋪開,其內部結構的整潔程度,比江臨原本想像的要乾淨得多。

  根目錄下躺著三個子文件夾。

  江臨點開/baseline

  裡面赫然是三套經典的微內核基礎算法的基準實現:rank5(五元素求秩)、sort5(五元素完全排序)、top3_of_8(八元素選前三)。

  每一套算法,陳啟明都嚴謹地給出了兩份截然不同的代碼。

  一份是沒有任何底層優化的純C語言可讀版本,用來錨定邏輯正確性。

  另一份,則是陳啟明團隊經過多年打磨的手寫極限優化版。

  江臨掃了一眼,後者的代碼里充斥著晦澀的編譯器內聯提示,強制循環展開,以及少量依賴特定CPU令集的平台相關寫法。

  江臨退出來,點開/verify。

  裡面是幾十個詳盡的驗證腳本,涵蓋了各種極端的邊界測試用例。

  最後是那個最讓系統工程師頭皮發麻的/bench(性能測試)文件夾。

  裡面只有一份原始的.csv格式的性能計數統計表。

  這張表是在三台底層微架構完全不同的企業級伺服器上,經過漫長的馬拉松式壓測跑出來的。

  江臨將表格放大。

  行末密密麻麻地標註著測試環境的絕對變量:CPU 型號,微碼更新版本號,L1/L2緩存的命中率,分支預測失敗率,以及頻率是否強制鎖定。

  頻率強制鎖定這一項讓江臨頗為讚許。

  現代商用CPU都會狡猾地根據溫度和負載,動態調整時鐘頻率。

  在進行這種納米秒級別的內核代碼測試時,哪怕CPU的頻率發生了一次輕微的抖動,都會導致測試出的時鐘周期出現巨大的噪聲,從而徹底掩蓋掉代碼優化帶來的那一兩納秒的真實提升。

  陳啟明那幫人不僅清楚這一點,還暴力地在BIOS層面鎖死了頻率。

  光這一張數據表就無聲地證明了,陳啟明這幫人,是真正懂行的老手。

  他們已經把人類能夠手動控制的變量,極致地推到了物理的極限。

  這就有意思了。

  江臨決定先從看起來最最基礎的sort5開始解剖。

  題面很簡單:給定五個隨機的整數,將它們按從小到大的順序排列好。

  任何一個學會寫for循環和if語句的新手,都能在短暫的三分鐘內,交出一段基於兩層嵌套循環的冒泡排序或者插入排序代碼。

  當然,這僅僅只是能跑通的玩具。

  陳啟明這種長期和底層性能打交道的人,要的當然不是這種充滿分支跳轉,在現現代CPU流水線里到處添堵的低效正確。

  而在去追求那個極致的快之前,江臨的數學直覺告訴他,必須先解決一個哲學問題。

  【怎麼用嚴謹的數學邏輯,去證明一段晦澀的排序代碼,對全宇宙所有可能出現的輸入組合,都百分之百地正確?】

  最直觀也是最笨的辦法,是暴力地把這五個數的全部大小關係組合,挨個枚舉一遍。

  那麼五個數到底有多少種不同的全排列?

  簡單的組合數學。

  5!

  一百二十種。

  這個數字小到機器一瞬間就能跑完並驗證。

  這讓江臨難免就想起了那塊磚。

  做江氏磚的時候,刻進他骨頭裡的數學動作,就是把一個龐大到趨於無窮的邊界狀態空間,精妙地壓縮成機器能窮舉人能覆核的有限狀態形式。

  一百二十確實不大。

  可如果是頻繁出現在高級資料庫索引中的,排八個數,排十六個數呢?


  8!

  四萬種,機器依然能夠輕鬆秒殺。

  但是到了十六個數。

  16!

  二十萬億級別。

  對普通程序來說,這已經不是多跑一會兒的問題,而是足以把最笨的全排列驗證拖進泥潭。

  如果MPS框架建立在這種愚蠢的全排列窮舉上,它將迅速死在起跑線上。

  江臨轉了一下手中的原子筆,大腦的記憶宮殿開始高速檢索。

  很快,他放下了筆,在鍵盤上敲下了一行學術檢索詞。

  zero-one principle sorting network

  (排序網絡:零一原理)

  他其實早在廢土時間裡,在啃噬那些浩如煙海的計算機科學巨著時,就已經做了相關的知識儲備。

  只是在這之前,它僅僅只是停留在離散數學課本里一條定理這樣的程度上,並沒有迫切的用武之地。

  而現在,江臨認為這條優美的定理可以成為MPS-Kernel的第一塊地基。

  在理論計算機科學中,零一原理可以高傲地宣稱——

  一個由比較—交換操作固定地構成的比較網絡,它是一個絕對正確的排序網絡,若且唯若,它能夠極其正確地將所有僅僅由數字0和數字1構成的有限輸入序列,完全排好序。

  這條定理的殺傷力在於,它無情地斬斷了無限與有限的邊界。

  不會要求你去檢驗全部的120種排列,也不要求你往這段排序代碼里餵進任何一個帶有具體數值的實際輸入。

  它只要求你檢驗那些由0和1拼湊出來的二進位序列。

  對於sort5(五個位置),根據零一原理,可以看做每個獨立的位置上,要麼是絕對的0,要麼是絕對的1(非 0 即 1)。

  於是,它的驗證空間一下子就被不可思議地坍縮成了2⁵=32。

  只要你寫出的這段代碼網絡,能夠正確無誤地把這三十二個全由0和1構成的序列排成單調遞增的形狀

  那麼,神奇且絕對的是,這段比較網絡,對任何來自同一全序類型的五個輸入都正確。

  原本的微小的120,被再次降維壓成了32。

  如果是十六個數,原本極其恐怖的二十萬億,也能壓縮成六萬五千個。

  一個微處理器閉著眼睛都能跑完的數字。

  更重要的是,這三十二個簡陋的0-1序列,還不是軟體工程里充滿玄學的抽樣測試,也絕對不是測試工程師絞盡腦汁想出來的邊界測試用例。

  它在數學意義上,是不留死角的完備檢查。

  只要順利地跑通這三十二個簡單的序列,這段代碼底層的絕對正確性,就被焊死在了真理的鐵板上。

  這種利用抽象的數學定理斬殺無限狀態空間的快感,簡直太美妙。

  江臨立刻在MPS-Kernel根目錄下新建了一個Python腳本文件。

  verify_sort5_zero_one.py

  引入itertools.product,暴力地生成全部確定的三十二個0-1序列。

  對每一個獨立的序列,機械地跑一遍外部掛載的候選排序網絡代碼。

  嚴格地檢查輸出序列是否滿足單調不減。

  三十二個嚴苛的證人,只要全部通過,程序就會返回綠色的【PROVEN VALID】(證明有效)。

  而只要有任何一個微小的序列無法通過,程序就會將那個反例直接吐出來,無情地槍斃這段代碼。

  三十二個全過,返回已證明。

  任何一個不過,把那個反例吐出來。

  江臨敲下回車鍵,拿陳啟明團隊提供的那份原始的 /baseline/sort5_pure.c 暴力地跑了一遍夾具。

  綠色。

  三十二個0-1證人,在嚴密的數學法庭上,沒有一個翻供。

  正確性的地基,有了。

  接下來的是神仙打架的活:在所有正確的排序網絡里,找出最好的那一個。


  江臨開始把他在鋪砌幾何中經過千錘百鍊的MPS搜索骨架,巧妙地往這個代碼問題上套。

  做磚時,狀態是一塊局部鋪砌,動作是放下一塊新磚,勝利條件是排除所有拓撲逃逸。

  現在呢?

  狀態是一段已經寫下的比較器序列。

  動作是往後追加一個比較器,比較兩個位置,把小的甩前,大的甩後。

  勝利條件,是這段序列讓三十二個證人全部點頭。

  結構一模一樣。

  只是把幾何換成了指令。

  他寫了一版搜索:從空網絡出發,逐個追加比較器,每加一個就用零一原理剪枝,留下還有希望的分支,砍掉已經走死的。

  搜索先跑長度八以內的全部候選。

  MPS沒找到任何一個能讓三十二個0-1證人全部點頭的網絡。

  然後長度放到九。

  第一組通過的網絡出現了。

  江臨把結果和Knuth里那行S(5)=9對上。

  這才意味著:五個元素排序,九個比較器不只是能做到,而是最低限度。

  教科書上寫了幾十年的那個數字,被他這套從一塊磚上長出來的框架,從頭獨立搜了出來。

  框架,遷移成功。

  江臨靠回椅背,看著屏幕上那九行乾淨的比較器。

  一瞬間,有種成了的輕飄飄然。

  可這點輕飄只浮起了幾秒,就被他自己一把按了下去。

  重新調出陳啟明那張流血的火焰圖。

  median7_fast、rank5_inline、top3_window……

  陳啟明早就說過,人工優化的紅利,已經被那群長期泡在底層代碼里的人壓得很薄了。

  意思是他團隊現有的代碼,比較器數量大概率早就是九,或者貼著九。

  而他用高維數學和搜索算法搜出來的最少九個比較器,對陳啟明而言,根本不是什麼新東西。

  江臨盯著自己那九行結果,眉頭慢慢擰緊。

  他犯了一個新手才會犯的錯。

  下意識地去優化了那個最乾淨最好定義也最容易寫出適應度函數的組合目標——比較器的數量。

  可陳啟明要的,從來不是數學意義上的數量最少。

  而是在他那幾台具體的伺服器上,跑得最快。

  數量最少和跑得最快在真實的物理世界裡,根本不是一回事。

  江臨想起陳啟明在報告廳留下的最後一句話。

  他的搜索空間裡,充滿了正確的低效垃圾。

  此刻他更深一層地明白了。

  九個比較器,是按數量排的。

  但在一顆真實的CPU里,決定一段代碼快慢的,從來就不只是指令的數量。

  而是這九個比較器之間的數據依賴鏈:哪些必須排隊等前一個算完,哪些可以並排著同時算。

  是它們落在亂序執行引擎的哪幾個埠上,會不會擠在一起搶資源。

  是每條min、max、條件傳送指令的延遲和吞吐。

  是寄存器夠不夠用,會不會被逼著往內存里倒騰。

  同樣九個比較器,排成一條又細又長的依賴鏈,和排成五層能並行的淺塔,在流水線上的表現,可能差出一大截。

  比較器數量少,和比較器深度淺,是兩個不同的目標。

  陳啟明真正想要的目標,那個在型號A的伺服器上榨乾最後一滴性能,把延遲壓到最低的最優解,藏在極深極髒的硬體底層里。

  和微架構死死綁在一起。

  換一顆不同代際的CPU,哪怕只是從Intel的Skylake換到Zen 3架構,緩存延遲和指令埠的微小變化,都會導致那個最優解瞬間跌落神壇,變成次優。

  江臨盯著那張三台機器的perf表,一下子意識到,要找到那個真正適應物理世界的最優解,單靠數學證明是不夠的。

  他必須在他的MPS框架里,接入一個代價模型。


  在海量正確的候選網絡里,不僅看數量,還要看深度,看並行度。

  甚至到最後,他需要寫一個自動化腳本,把篩選出來的,看起來很有希望的幾百個變種網絡,一個一個編譯成機器碼,扔進真實的物理伺服器里去實測,打分,篩選。

  每一次實測都伴隨著作業系統調度的噪聲、緩存預熱的波動。

  他得每個候選跑上一百萬次,取中位數,取99分位延遲,做枯燥的統計學對抗。

  根本不是一個晚上,甚至不是現實里的一年半載能夠跑完的事。

  意識到純算法在底層硬體面前的局限性後,江臨的腦子反而冷靜了下來。

  順手點開rank5,這個邏輯能不能也用現有的框架碾壓過去。

  題面:給五個數,不要求全部排好,只問輸入窗口中心位置的那個數,在這五個數里到底排第幾。

  幾乎是出於慣性,本能地就想把剛寫好的零一原理驗證腳本套上去。

  不過還好下一刻,他就及時把自己摁住了。

  零一原理管的是排好沒有,是全局的單調性。

  可rank5要的根本不是把所有數都乖乖排好,而是要給每個數,或者特定的某個數,貼上一個精確的名次標籤。

  如此,它的正確性判據不再是問最終的輸出序列有沒有單調遞增,而是變成了問:那個原本在輸入中心位置的數,它頭上頂著的名次,到底是不是正確的名次?

  如果強行把輸入全換成0和1會發生什麼?

  比如原始輸入是【10, 50, 30, 20, 40】,中間那個數是30,它排第三名。

  如果粗暴地二值化為0-1序列,可能會變成【0, 1, 1, 0, 1】。

  在這個二值序列里,有三個1,兩個0。

  原本該區分開的絕對名次情形,因為數值維度的坍縮,直接撞成了一團爛泥,根本分不開誰是真正的第三。

  零一原理在這裡,不直接成立。

  或者說,它失效了。

  rank5≠排序網絡。

  驗證rank5,必須回到相對大小關係(如置換群)的層面,而非單純的0-1輸入空間。

  它的底層結構更接近一張由偏序關係構成的,動態更新的兩兩比較矩陣。

  這又是一個新坑。

  江臨的目光掃過最後一個問題:top3_of_8。

  從八個數里,挑出前三大。

  這個問題同樣暗藏殺機。

  對比較網絡形式的top-k選擇,類似的零一檢驗可以使用。

  八個位置,就是2^8=256個0-1證人。

  但前提是,你必須先和出題人把正確的語義定義好。

  什麼叫挑出前三?

  口徑A:只要最大的三個數,落進了輸出數組的前三個坑位就行,這三個數內部是亂序也無所謂?

  口徑B:還是說,不僅最大的三個數要進前三,而且這三個數之間,也必須嚴格按照從大到小排好?

  如果口徑沒有定死,那麼MPS框架搜索出來的就是空中樓閣。

  標準寬泛一分,搜索空間就呈指數級縮小。

  標準嚴格一分,依賴鏈就不可避免地加長。

  三個題,三套完全不同的脾氣,三種不同的驗證體系。

  江臨沒有急著去寫代碼解題。

  因為方向比速度重要一萬倍。

  他打開一個文本編輯器,像一個在雷區插旗的工兵一樣,把每一套題目的邏輯邊界、驗證難點、硬體耦合點,一字一句地記進文檔里。

  天快亮的時候,江臨整理出了一份能交給陳啟明的東西。

  第一樣,是基於零一原理的驗證夾具。

  這是一個殺器。

  它能對陳啟明團隊裡任何人寫出的任何一段sort5候選代碼,給出一份絕對窮舉的,與底層CPU架構毫無關係的,在數學上不可辯駁的正確性證明。

  以後,誰要是交上來一段自以為絕妙的位運算代碼,不用爭吵,跑一下這個夾具。


  三十兩個0-1證人當場表態。

  不過關的直接打回。

  這就是地基。

  第二樣,是一份邊界說明文檔。

  他把陳啟明面臨的混沌問題,一分為二。

  第一層,是純組合層面的理論目標。

  比較器的總數能不能更少?

  依賴鏈的層數能不能更淺?

  對sort5這種規模的小問題,江臨承諾,他可以用他的MPS框架,把這一層的所有理論最優解完整摸清,連根拔起。

  但他也直言不諱地指出,面對更大的n(比如16、32),哪怕是MPS,依然會在算力面前發生組合爆炸。

  必須引入啟發式剪枝。

  第二層,是與具體物理硬體綁定的微架構最優。

  也就是在陳啟明指定的某一台伺服器上,把延遲壓到最低。

  這一層,需要陳啟明的實測夾具,需要海量的候選代碼去真機里跑統計。

  工作量極大,需要時間。

  這絕非現實世界裡加幾個通宵的班就能夠解決的問題。

  他會把整套MPS-Kernel的框架,搬進廢土裡去打磨。

  在那裡,他有幾十年,去把搜索框架,剪枝策略,代價模型一寸一寸地養肥,調通,跑穩。

  當然,前哨站里那台工作站,是他從現實背進去的特定機器。

  它跑出來的最快,只是那台機器上的最快。

  陳啟明的目標,是另外幾台型號完全不同的伺服器。

  微架構一換,最優解就不一樣。

  所以,廢土那幾十年,他真正能帶回來的,不會是某一段在廢土硬體上飛快的代碼。

  而是一套被反覆打磨調試,驗證過的超級搜索框架,一套成熟的代價建模方法論,外加一個經過數學驗證和現實實測雙重篩選的候選網絡結構庫。

  如此計劃妥當,江臨將打包好的文檔和腳本重命名為J_MPS_Phase1_Deliverables.zip。

  窗外天光已經泛白,江城那場憋了一夜的雨,到底沒落下來。

章節目錄