為什麼我們要分層建造
在前兩篇導覽中,你認識了儲存程式的概念,以及馮紐曼模型——一個控制單元與資料路徑從同一個記憶體讀取程式碼與資料。但寫應用程式時沒人真的會去想電晶體,設計電晶體時也沒人會去想你的應用程式。整個領域靠一個單一的把戲存活:抽象。每一層對上層提出一個乾淨的承諾,並把底下一灘細節的爛泥藏起來。
把它想成一棟公寓大樓。住戶按下電燈開關就期待有光;他們從不去推敲牆裡的線路、街角的變電所,或是一百英里外燒著瓦斯的發電廠。每一層都信任下層會守住它的承諾。電腦也是這樣運作的——而那個讓我們整個產業的兩半(軟體與硬體)對承諾達成共識的咒語,就是硬體—軟體介面。
向下的電梯之旅
讓我們拿一行平凡的程式——比方說 `total = total + price`——一路送到矽片上。最頂端坐著應用程式,也就是你實際使用的程式。緊接在下方的是高階語言(Python、C、Rust),那行程式就住在這裡。一個編譯器加上作業系統——我們稱之為編譯器與作業系統的角色——翻譯並管理底下的一切,把你友善的那一行變成機器指令,並借給它記憶體、檔案,以及處理器上的時間。
下一層才是關鍵:指令集架構(ISA)——晶片所理解的實際操作詞彙,對人類寫成組合語言,對機器寫成二進位。這就是那份著名的合約。在它之上,寫軟體的人只承諾講這套詞彙;在它之下,硬體設計者承諾遵守它,但可以用任何他們喜歡的方式實作。RISC-V 的 `add x5, x5, x6` 就是這樣的一個詞。
在 ISA 之下,我們離開了「架構」、進入微架構——真正執行每條指令的資料路徑與控制單元,可能還帶著管線或快取。那條資料路徑由邏輯閘建構而成;每個邏輯閘由少數幾個電晶體開關組成;而每個電晶體是矽片裡一個由電壓控制的微小水龍頭。九到十個誠實的謊言,從你那行程式碼一路疊到一個量子力學的開關。
application total = total + price high-level language in Python / C / Rust ---- compiler + OS translate & manage ---- ISA (the contract) add x5, x5, x6 <- architecture ===================== contract line ===================== microarchitecture datapath, pipeline, cache <- organization logic gates AND / OR / NOT, adders transistors voltage-controlled switches silicon doped regions on a wafer
架構對上組織:那條關鍵的線
這疊抽象中唯一最重要的邊界就是合約線,而初學者老是在這裡跌倒。架構就是 ISA——那份看得見的合約:有哪些指令、有多少暫存器、一個字組(word)有多大、一個位址代表什麼。組織(在口語中近乎微架構的同義詞)則是實作:管線有多深、快取有多大、晶片裡有幾個加法器。同樣的架構,可以有天差地遠的組織。
為什麼這道劃分如此重要?因為它讓同一個程式能在同一系列的小手機晶片與巨大伺服器晶片上都跑得起來——兩者都遵守合約,所以你編譯好的二進位檔就是能用。手機晶片可能一次只跑一條指令以省電;伺服器晶片可能大量採用管線並在內部重排工作。但兩者都不准改變程式所觀察到的結果。這正是 ISA 的全部意義:它把「做什麼」與「怎麼做」解耦。
位元、位元組、字組與時脈
每一層最終都是用位元在說話,因為電晶體只認得開或關。八個位元組成一個位元組(byte),這是最小的可定址單位;一個字組(word)是晶片天生的處理塊——在今天的機器上是 32 或 64 位元——這也是為什麼我們說一顆 CPU 是「64 位元」。數字靠巧妙的編碼住在這些字組裡;舉例來說,二的補數讓同一個加法器能同時處理加法與減法,因為減法不過就是加上負數,而取負就是「把位元全反轉再加一」。
在資料路徑底下跳動著一個時脈:一道方波,每秒滴答比方說三十億次。每滴答一下,機器就走一個同步的步伐。時脈頻率(3 GHz 代表每秒三十億次滴答)定下了節奏——但這裡有整個主題的第一個誠實警告:光是時脈頻率高,並不代表電腦比較快。那個誘人的信念就是著名的「百萬赫茲迷思」,而下一節將把它拆穿。
鐵律:它到底跑多快?
一個程式要跑多久,可以用一條你接下來一輩子都會用到的乾淨方程式來捕捉——效能的鐵律。CPU 時間 = 指令數 x CPI x 週期時間。由左讀到右:你跑了多少條指令,乘上每條指令平均花掉的週期數(CPI,cycles per instruction),再乘上一個週期持續多久(時脈頻率的倒數)。三個旋鈕,要贏,三個都得轉。
現在百萬赫茲迷思乾淨地破滅了。把時脈拉高,只縮短了週期時間——那第三項。如果一顆「比較快」的晶片需要更多指令,或每條指令卡住更多週期,那即使盒子上的數字更大,它也可能輸。更好的編譯器降低指令數;更聰明的微架構降低 CPI;而只有電晶體的物理特性能降低週期時間。真正的加速來自改善正在傷害你的那一項,而不是追逐千兆赫。
- 假設一個程式跑 1,000,000,000 條指令,平均 CPI = 1.2,在一顆 2 GHz 的晶片上(週期時間 = 0.5 奈秒)。CPU 時間 = 1e9 x 1.2 x 0.5 奈秒 = 0.6 秒。
- 行銷把時脈加倍到 4 GHz(週期時間 0.25 奈秒),但更深的管線把 CPI 推高到 1.8。新時間 = 1e9 x 1.8 x 0.25 奈秒 = 0.45 秒——更快了,但只快了 1.33 倍,而不是時脈所暗示的 2 倍。
- 現在一個更聰明的編譯器在同樣的 2 GHz、CPI 1.2 下,把指令數砍到 0.8e9。新時間 = 0.8e9 x 1.2 x 0.5 奈秒 = 0.48 秒——幾乎追平了拉時脈的成果,卻完全不需要新硬體。
為什麼硬體不再自己變快
數十年來,鐵律的第三項一直免費地縮小。摩爾定律——其實是一項觀察,而非自然律——說一顆晶片上的電晶體數量大約每兩年翻一倍。它較安靜的夥伴Dennard 縮放則說,當電晶體縮小時,它們也變得更快、每單位面積耗的功率更少,所以每一代都能在同樣的功率預算下單純地跑更高的時脈。在一段輝煌的時期裡,去年的程式今年就跑得更快,而你不必出半點力。
大約在 2005 年,Dennard 縮放崩潰了。電晶體繼續縮小,但它們每單位面積的功率不再下降——把時脈推得更高,晶片就會熔毀。這就是功率牆,也是整個產業轉向的唯一理由。我們再也無法讓單一核心更快,於是我們把許多核心放上一顆晶片(多核心),並為能平行化的工作打造像 GPU 這樣的專用引擎。免費的午餐結束了;如今速度必須靠刻意撰寫善用平行性、區域性與正確硬體的軟體來掙得。