從 Pentium 回顧 x86 處理器到底哪裡難做

這些年來,相信各位閒閒沒事,就會在網路各角落看到,不同領域的各路英雄好漢一直有相同疑惑:為何今天的 x86 處理器市場,檯面上只剩下英特爾和 AMD 兩家美國公司?頂多再加個存在感稀薄的台灣 VIA,和少人知悉的俄羅斯 Elbrus?對技術有點基礎認知的人,多少會直接想到「x86 指令集很複雜很難搞,又有英特爾的授權問題,所以 x86 處理器非常不好做」之類的標準答案。

當然,更不乏某些夜郎自大的高論,像「就算 x86 指令集再複雜,控制單元也還是很好設計」、「微指令轉譯早就克服了所有 x86 指令集的瓶頸」等。拜託,「做出勉強能動的東西」和「開發出有市場競爭力的產品」完全是天差地別的兩件事,要不然你以為英特爾和 AMD 天文數字般的產品開發經費,都拿去填海了嗎?

總之,高效能 x86 處理器之所以難做,混雜了諸多技術和商業因素,暗藏在檯面下的細節非外人足以道也。但我們可以回到 x86 處理器最重要的歷史轉捩點:1993 年 Pentium 處理器,抽絲剝繭一般人難以察覺到的蛛絲馬跡。

以「唯偏執狂得以生存」(Only the Paranoid Survive)留名於世的英特爾創辦人之一 Andy Grove 曾經說,2003 年的 Centrino 是英特爾的「第二個兒子」與「十年來最重要的品牌」,那「第一個兒子」和「十年前最重要的品牌」,想當然爾就是 1993 年的 Pentium 了。

Pentium 之名起源於希臘文的 Penta(意思是「五」),再加上拉丁文的 ium 結尾,意指「第五代 x86 處理器」,英特爾自此也不再使用 80×86 來定義處理器世代(要不然現在還真的講不出來到底是「幾 86」)。而 Pentium 也在不知不覺中,一步步轉型為入門級低價處理器品牌,更不再受限於 1993 年的 P5,進而橫跨歷代英特爾的超純量(Superscalar)x86 微架構。

原始 Pentium 裡的 P5 處理器微架構,並沒有像後繼 Pentium Pro 的 P6 活得如此之長,影響又如此深遠(到 2011 年 Sandy Bridge 才結束)。但 Pentium 問世的時機,卻是眾多歷史機運的集大成:

  • 「x86 處理器走向高效能化」。
  • 「x86 開始與 RISC 正面競爭」。
  • 「x86 指令集的缺陷讓廠商感到棘手」。
  • 「x86 處理器進軍多處理器平台」。
  • 「個人電腦市場因 Windows 95 的問世而蓬勃成長」。
  • 「筆記型電腦即將逐漸普及」。
  • 「英特爾默默埋下讓擅長改良的以色列海法研發團隊,主導 x86 處理器技術發展」。
  • 「x86 指令集的相容性,成為其他競爭者的潛在障礙,也造成軟體開發商的困擾」

這些因素交錯,奠定了之後 25 年技術演進與市場發展的基礎邏輯,連時下「AMD 處理器的核心太多,竟然造成 Windows 作業系統的大麻煩」,也和 Pentium 留下的遺產,多少有些千絲萬縷的糾結。

既然連 AMD Zen 成功的背後,都有這麼多不為人知的故事,了解英特爾「Landmark」晶片 Pentium 的軌跡,將有助跳脫琳瑯滿目的技術行銷名詞,重新建立屬於自己的「x86 處理器世界觀」,值得各位細細品味。

x86 指令集先天不足後天失調的原罪

催生「計算機結構」(Computer Architecture)一詞的「指令集架構」(Instruction Set Architecture),為「電腦的基礎語言」與「軟硬體中間的介面」(Interface Between Hardware and Software),相同指令集的電腦理應可執行一樣軟體,具備彼此相容性,對電腦與處理器微架構(Microarchitecture)的未來發展有舉足輕重的影響力。

不同的時空背景,也會產生相異的指令集設計理念,沒有絕對的優劣對錯──x86 算是少見的例外,另一個則是 DEC VAX,兩者的共同點只有「著毋庸議」的糟糕,英特爾 IA-64(Itanium)和 DEC Alpha 的激烈反動,足以證明連生父都如此厭惡小孩。

說到指令集架構如何影響處理器微架構的設計,這幾年來最有名的實例,就是蘋果利用充分掌握封閉平台的優勢在「最短時間內驅逐 32 位元應用程式」,掌握 ARM 指令集邁向 64 位元的過程帶來的改革與機會,讓 A7 之後的自家 ARM 處理器,徹底為 64 位元的 ARMv8-A 量身訂做,研發出一系列能同時有效處理更多指令的先進微架構。

所謂的 CISC(複雜指令集電腦)出自記憶體容量稀少、缺乏成熟的高階語言編譯器、大多數人使用語言撰寫程式的時空背景,程式設計者寄望直接用透過微碼(Microcode)組成功能強大的單一指令,像十進位數字和複雜字串處理等,撰寫應用程式。

相較之下,RISC(精簡指令集電腦)則是在「萬事皆備」的環境,追求更快更便宜的電腦,將電晶體預算砸在「最經常被使用的簡單指令和運算元定址模式」的刀口上,盡量用硬體線路去取代微碼,並藉由強制僅載入(Load)/儲存(Store)指令可存取記憶體,搭配大型化的資料暫存器與快取記憶體,填補處理器和記憶體之間越來越大的效率鴻溝。

時過境遷,在 x86 主宰雲端資料中心、中低階伺服器、工作站、桌機一路到筆電的今日,可能已經沒有多少人能回想起「RISC 與 CISC 之爭」曾發生在 1990 年代初期的史蹟,但畢竟 x86 指令集公認是充滿缺陷的產物,連當代最偉大的計算機結構教科書都「白紙黑字」並「燒錄」於無數莘莘學子的腦中,而 AMD K5 的創造者更是用一句「毫無道理可循」(it just doesn’t make a lot of sense)一鎚定音,幾無爭議空間。

讓人滿臉黑線的,只有動輒揮出「逆向思考全壘打」的大恩大德,將 x86 的市場勝利,視為「x86 指令集架構優良」佐證的天真論點,並時有所聞,連 ARM 都比 x86 更「記憶體存取密集」(Memory-Intensive)這種話都講得出口。

我們先來瞧瞧當年 AMD K5 的總工程師 Mike Johnson 怎麼評論 x86 指令集,由設計 x86 處理器的大師級人物(他本人的確是超純量管線技術的先驅者,那份變成首本超純量技術專書的史丹佛大學博士論文非常有名,附錄更深度分析設計超純量 x86 處理器的困難點)寫出來的批評,特別有說服力,也一再被後人引述:

The complexity of the x86 is not an impassable barrier. The x86 really isn’t all that complex—it just doesn’t make a lot of sense…. The biggest weakness in the x86 instruction set is the lack of registers coupled with an extremely painful addressing scheme.

從這短短一段話,可看到幾個重點:

「毫無道理可循」(doesn’t make a lot of sense):相較於指令編碼程度統──4 Bytes(32 bits)的多數 RISC 體系,x86 指令集的格式和編碼極度混亂,長短粗細肥瘦不一,從 1~17 Bytes 都有可能。影響所及,遍及所有的環節,從快取記憶體擷取指令、實作指令管線化,到處理器發生中斷例外時要迅速儲存執行狀態並儘快回復等,都造成非常嚴重的後遺症,激增研製高效能 x86 處理器的門檻,也增加驗證產品的時間與成本。

「缺乏足夠的暫存器」(the lack of registers):一般 RISC 指令集都會定義 32 個通用資料暫存器(講的精確一點是 31 個)或浮點運算暫存器,但 x86 在 64 位元和 AVX 之前,怎麼樣都只有少少的 8 個(因為要扣掉 ESP 和 EBP,說 6 個或許比較貼切)。當引進指令管線化和更先進的超純量管線後,自然就激增了暫存器衝突的機率,這也是激發 x86 處理器擁有強大非循序指令執行(暫存器重新更名)與高效率記憶體子系統的主因。

「讓人感到極度痛苦的定址模式」(extremely painful addressing scheme):定址模式講得白話一點是「存取所需運算元(運算的目標,例如某個暫存器或某個記憶體位址)的方式」,歷經多年疊床架屋的 x86 定址模式,尤其是惡名昭彰的節區記憶體(Segmentation),不只加重產生有效位址的工作負荷,也激增整數邏輯運算(ALU)和控制單元的複雜度,「副作用」跟第一項「毫無道理可尋」可謂不相上下。

這些燙手山竽,就是 1980 年代末期和 1990 年代初期,眾多企圖研製高效能 x86 處理器的有志之士,包含不小心製造出這些「炸彈」的英特爾,不得不面對並設法克服的挑戰,身為 x86 世界首顆「超純量(Superscalar)管線」處理器的 Pentium,則是第一個提出的「有效解決方案」,英特爾為此付出了不小代價。

一個時脈週期執行一個以上指令的「超純量管線」

近似近代工業生產線的概念,讓所有人都「閒閒有事做」的管線化(Pipeline)一直是提高效率的最基本手段,80386 的預先指令擷取(Prefetch)已有雛型,而 80486 是 x86 世界首款真正達成指令管線化的處理器,為了確保記憶體跟得上運算需求,也配置指令/資料共用的第一階快取記憶體,雖然真正「每個時脈週期都可穩定輸出」者,也只限於簡單的整數邏輯運算指令。

套句台灣某前總統被罵翻的名言:一個便當吃不飽,你可以花錢買第二個。執行一個指令不夠多,當然得想出辦法硬塞第二個。「一個時脈週期內執行一個以上指令」的超純量管線(Superscalar),早在 1965 年發跡於 RISC 始祖的 CDC6600,1980 年代陸續出現在 RISC 處理器,如 1985 年 Power 前身的 IBM「America」計畫(也是「超純量」一詞由來)、1988 年 Motorola MC88100、1989 年英特爾 i960CA、1990 年 AMD 29050 等。各位絕對沒看錯,英特爾、AMD 也做過 RISC 處理器。

指令管線化、超純量管線、非循序指令執行、大型化快取記憶體等常見的效能加速手段,清一色優先降臨 RISC 處理器的原因,也很簡單:RISC 指令集的單純性與簡潔性,讓設計者更輕易導入這些技術,並用更短的時間完成產品開發與驗證。

1990 年代上半期,一提到高效能處理器,幾乎都由 RISC 獨領風騷,像至今碩果僅存的 IBM Power、HP 的 PA-RISC、SGI 的 MIPS、Sun 的 UltraSPARC、Fujitsu 的 SPARC64 及充滿傳奇色彩的 DEC Alpha 等,這也是「RISC 優於 CISC」的理論基礎。

但「理論」是一回事,不代表 CISC 的 x86「實務上」做不到,更何況又是擁有一整支龐大「研發軍隊」的英特爾,革命性的第五世代 x86 處理器 Pentium 在 1993 年 3 月 22 日登上歷史舞台,不只要迎擊來自 AMD、Cyrix、NexGen(被 AMD 購併,Nx686 變成 K6)的競爭產品(之後還多出 Centaur 和 Transmeta),更要面對 AIM 聯盟(Apple、IBM、Mororola,不是美國的空對空飛彈)PowerPC 的挑戰,後者享有威鎮四方的「RISC 王者」IBM Power 當強大後盾,市面更不乏「專書」鼓吹 PowerPC 相對 x86 的優越性,把 Pentium 批評得一文不值。

更扯的是,意圖搶奪英特爾勢力範圍的 IBM 還開發了「腳位與 Pentium 相容,可以同等效率硬體執行 x86 程式碼,並兼具 32 / 64 位元 PowerPC 指令集相容性」的 PowerPC 615,要不是微軟可能覺得難搞,不符成本效益,拒絕支援這顆處理器,導致永遠無法量產,英特爾的處境會更危險。至於 PowerPC 615 取消後,研發團隊就投靠 Transmeta,接著就無疾而終了。

也許各位難以想像「為什麼 x86 會受 PowerPC 威脅,不是屬於不同市場嗎」,但此時此刻,沒半個正常人會將連伺服器市場邊都沾不上的 x86 和「高效能」三個字聯想在一起,甚至連那時候英特爾內部,都很少人願意相信 x86 還有未來性,否則也不會出現 IA-64 指令集和 Itanium 處理器了。況且微軟 1993 年 7 月發表未來作業系統基礎的 Windows NT,打從一開始就同時支援 x86、MIPS 和 Alpha 三版本,之後還加碼 PowerPC 和 IA-64,「不將雞蛋放在同一個籃子裡」意圖太明顯了。

換句話說,英特爾當時的戰略地位,並不像今天如此牢不可破,更早在 1990 年同步啟動第六世代 Pentium Pro 研發案,完全沒有承受失敗的本錢與餘裕。

天底下沒有白吃的午餐

Pentium 是如假包換的 x86 世界首款超純量處理器,可在同一個時脈週期內執行最多兩個指令,整體結構近似雙重管線的「放大發展版」80486,但也因扛著 x86 指令集的原罪,由外到內付出了不少代價。爬文至此,建議各位回頭複習一次 Mike Johnson 評論的 3 個重點,你一定會更有感觸。

我們光從初代 Pentium(P5)的晶粒圖,即可清楚看到 x86 相容性的代價:大型化的快取記憶體(儘管實質容量不高,但內部結構卻出奇複雜)、巨大的指令擷取單元、解碼與複雜指令微碼控制單元(Complex Instruction Support),這也是日後所有高效能 x86 處理器的共同特色。

指令/資料分而治之的第一階快取記憶體:x86 指令集因缺乏足夠的資料暫存器,且包含了大量「暫存器/記憶體互通有無」與直接以記憶體為運算目標的指令,不像 RISC 的「載入/儲存」架構「一次從記憶體抓了大量資料進來,算完後再一次性丟回記憶體」,特別需要強力的記憶體子系統,所以採用指令/資料分開的第一階快取記憶體,確保兩邊足以餵飽個別的需求。而 Pentium 的指令快取和資料快取,更是各自大有文章。

前面有提及「x86 指令最大長度是 17Bytes」,Pentium 第一階指令快取的內部結構,是以兩個最小存取單位 16Bytes 區塊,組成單一 32Bytes 的快取線(Cache-Line),假若發生最糟糕的情況,17Bytes 長度的指令「橫跨」了兩條 32Bytes 快取線,但仍希望一次擷取到指令管線,那該怎麼辦?Pentium 導入跨指令線分離式擷取(Split Fetch),可連續讀取橫跨邊界的兩條 16Bytes 最小區塊,而指令讀取緩衝區也是 4 倍於 80486 的 128Bytes,以確保擷取指令的效率可「餵飽」兩條管線的指令解碼器。

順便對照一下從 NexGen Nx686 發展而來的 AMD K6。AMD 取消了兩倍核心時脈的第一階快取記憶體,但除了既有的預先解碼位元(Pre-decoded Bits)用來標定指令邊界,再追加第二個指令存取埠,以應付這種狀況。反正各家廠商都各顯神通,直到可降低指令解碼器使用率的微指令快取(uOp cache)同時成為英特爾和 AMD 的制式武裝為止。

資料快取亦不遑多讓,資料快取具備 3 個存取埠,可同時應付來自快取資料一致性協定與雙重執行管線的需要,更進一步將每條 32Bytes 快取線,切成彼此交錯的 8 個獨立 4Bytes「Bank」,只要兩個資料存取需求不會同時使用同一個 Bank,即可在單一時脈內搞定。這是計算機工業史上的第一次嘗試,但這也大幅加重了快取記憶體的複雜度,也連帶不得不強化配置資料快取的虛擬/實體位址轉換緩衝區(TLB,Translation-Lookaside Buffer),並新增判斷 Bank 是否發生衝突的功能電路。

巨大的微程式唯讀記憶體:基於「加速常用的簡單指令」的理念,Pentium 的指令解碼器可直接硬體解碼大多數「暫存器→記憶體」與「記憶體→暫存器」之類的「相對簡單」運算指令,但 x86 歷代累積下來的龐大複雜指令,還是需要動用微碼組成微程式產生控制訊號。

Pentium 的單一微碼字元長度是 92Bits,總共存放了 4K 數量。換言之,產生了高達 47kB 容量的唯讀記憶體(ROM)空間,還遠多於第一階快取記憶體的容量(8kB+8kB),相當驚人,老舊指令相容性帶來的巨大負擔,由此可見一斑,這就是維持回溯相容性,所必須付出的昂貴代價。

4 個輸入的位址計算單元:一套所謂「複雜」的指令集,除了不規則的指令編碼長度,亂無章法的運算元定址模式和記憶體定址,更是必備的條件(可回顧一下 AMD Mike Johnson 講過的話)。實現高效能的超純量管線化 x86 處理器,並非只需弄好管線前端的指令擷取、解碼,與執行階段的存取記憶體,高效率的有效位址計算(Address Calculation)能力,更是 x86 有別於 RISC 體系的一大差異點,坊間人云亦云、積非成是的「x86 處理器只有指令解碼器比較難做」完全是大錯特錯的誤解。

為了加速記憶體位址計算,讓執行單位儘快得到「運算目標」,Pentium 的兩條指令管線個別有一套 4 個輸入值的加法器(4-Input Address Adder),對應 x86 指令集產生有效位址的 4 個數字:

  • 節區描述器(Segment Descriptor)提供的基底值(Base)。
  • 來自通用暫存器的基底位址(Base Address)。
  • 取自通用暫存器的索引值(Index,再加上scale)。
  • 指令編碼附上的的移位值(Displacement)。

因此部分僅支援 3 個輸出值的 486,需耗費兩個時脈週期完成位址計算的複雜指令,Pentium 只需一個時脈週期即可,更利於管線化執行指令。

但慘劇尚未劃下句點,x86 的節區記憶體(Segment),必須強制檢驗每個節區的大小,確保記憶體運算元落在節區描述器所定義的記憶體範圍內。80286 時代的保護模式,節區描述器會影響節區位置與體積的參數,總計有:

  • 32 位元基底值(Base)。
  • 20 位元範圍值(Limit)。
  • 範圍值單位 Page 或 Byte(前者上限 4GB,後者則 1MB)。
  • 針對堆疊(Stack)資料結構的向下擴展(Expand-Down)欄位。

處理器需採取不同的方式計算最高與最低位址,結果 Pentium 的兩條指令管線,為此個別又得「再」加上一套 4 個輸入值的加法器(4-Input Segment-Check Adder),為檢查節區正確性之用,而 486 的情況如上述位址產生器,須耗費更多時脈週期做這件事。

為何「位址計算單元」一向是歷代 x86 處理器增加執行單元的重頭戲,原因就在此。Windows 95 刺激個人電腦普及的年代,「32 位元最佳化」的 Pentium Pro 被批評「16 位元效能不佳」就因動到資料節區暫存器的指令,無法被非循序預測執行,會讓隨後的指令上演大塞車,到了 Pentium II 才修正。即使假以時日,這些老舊包袱的使用率只會越來越低,也早不再是改善效能的重點,但也沒人膽敢冒著犧牲軟體相容性的風險,根除這些歷史遺跡。

正面對決 PowerPC 且落居下風

相較於同時期且較早推出的超純量 RISC 處理器,就可明顯看出 x86 指令集的複雜度造成的負面影響。

以 1993 年秋季上市的 IBM PowerPC 601 為例,電晶體數目僅 280 萬,採用 0.6um(600nm)製程時的晶粒面積僅 121 平方公釐,卻有比電晶體 310 萬的 Pentium 更高的 80MHz 時脈、更大一倍的 32kB 指令/資料共用式第一階快取記憶體,與 1.5 倍的指令執行能力,而採用 0.8um(800nm)製程的初代 Pentium 是一顆 16.7×17.6mm、294 平方公釐的巨大晶片,完全瞠乎其後。英特爾當時就表示,相較於同等級 RISC 處理器,Pentium 有約 30% 電晶體都「貢獻」給 x86 指令集的相容性。不難想見那時候的「RISC 十字軍」有多 high。

而 Pentium 的雙重超純量指令管線也是限制重重,只有主管線「U Pipe」可以執行所有的 x86 指令,副管線「V Pipe」僅能負責比較簡單者,而要這兩條管線一起動,還需要依循指令配對規則,講白了就是「兩邊都要跑簡單指令」,且涉及暫存器和記憶體兩邊資料互相搬移的指令也無能為力,就是要強迫其中一條管線「發呆」兩個時脈週期給你看。PowerPC 601「理所當然」比較沒有這樣的煩惱。

80×87 浮點指令集更是 x86 處理器追求高效能浮點運算的罩門,因「英特爾內部溝通不良」(英特爾美國加州總部和以色列海法之間實在太遠了,1970 年代末期的聯絡手段又沒像今天這麼方便)誕生的「極度愚蠢」堆疊式(Stack)暫存器架構(附贈讓人摸不著頭緒的 80 位元延伸雙倍精確度浮點格式),強迫多數浮點指令的運算元,其中一個非得指定放在堆疊暫存器的頂端不可。

英特爾在 Pentium 加入 FXCH 指令用來交換置頂暫存器,原本僅內建一組浮點運算單元,管線不能同時執行兩個浮點運算指令的 Pentium,簡單的浮點運算指令可和 FXCH 一同塞進兩條指令管線,但實際上也只有執行一個有效浮點運算,況且後頭接連著的整數指令,都會被延誤最少一個時脈週期。

判斷分支條件需「借用」整數運算通用暫存器與執行單元,則是 80×87 另一個弱點,從一個浮點運算設定條件碼、將浮點運算的執行資訊搬移至通用暫存器、傳送至條件碼暫存器,再依據其結果,啟動正常的分支處理流程,Pentium 整整耗時 9 個時脈週期。當然可透過「插入」其他整數指令來降低效能損失,但無法彌補當執行條件判斷密集的程式,整數浮點單元之間反覆「踢皮球」的傷害。

這些在今天只會讓人覺得很荒謬的往事,讓 Pentium 的浮點性能仍遠遠不及同時期的 RISC 處理器,只能在 x86 的世界當大王,這宿疾到了新一代 Pentium Pro 依舊無解,同期 MIPS R10000 的 SPECfp92 浮點效能還是 Pentium Pro 的「3 倍」以上,還因為 PowerPC「外掛」AltiVec 而一度被拉開差距到差點看不見車尾燈的程度。

直到 Pentium III(Katmai)開始擴充 SIMD(單一指令,多重資料流)浮點指令集 SSE、初代 Pentium 4(Willamette)的 SSE2 新增雙倍精確度浮點格式,一路到 Sandy Bridge 的 AVX,引入 VEX(Vector Extension)標頭,一口氣解放了過去 x86 指令編碼帶來的重重枷鎖,才算功德圓滿。

但即使看似出師不利,x86 指令集的沉重包袱,並未讓英特爾就此停下腳步,依然持續精進 Pentium 處理器,就算沒有一鼓作氣打開天堂大門,卻也讓緊閉已久的門縫滲出充滿希望的曙光。

從企圖殺入很長一段時間內「可遠觀不可褻玩焉」的伺服器市場,預期 Windows 95 激發個人電腦市場爆發性成長時,補足高效能桌機和筆記型電腦需要的基本功能,到迎合「多媒體」的新潮技術行銷名詞,無不是英特爾 1990 年代初期念茲在茲的技術發展重點。這些努力的痕跡,統統一字不漏深深刻在 Pentium 和整個計算機工業的歷史上。

原汁原味的多處理器支援性

「多處理器支援性」是進入工作站與伺服器市場的最低門檻入門票,而 Pentium 則是 x86 歷史上首度「原生支援(Glueless)多處理器」的先行者,但嚴格說來,這到了 0.5um 製程的第二代 Pentium(P54C)才實現,而在此之前,也並不是沒有「多處理器 x86」的存在,只是需要外掛特製的系統晶片組,或連作業系統都要特殊版本。

一個便於實作的「無需外掛額外晶片」(Glueless)的多處理器(或多核心)環境,需具以下條件:

  • 分配、協調各 I/O 周邊裝置存取處理器需求的能力,發出中斷(Interrupt)時,知道該由哪個處理器負責:標準化的中斷處理機制。
  • 快取記憶體資料一致性協定(Cache Coherence Protocol):回寫式(Write-Back)快取記憶體常見的 MESI(Modified, Exclusive, Shared, Invalid)協議。
  • 低成本多處理器系統的根基:可讓多處理器共享的系統匯流排。

3 項條件之一,最重要者莫過於第一項。1983 年,17 名因英特爾極具野心的「32 位元微電腦大型主機」iAPX432 計畫失敗而離職的員工,創立的 Sequent Computer Systems(1999 年被 IBM 購併,研發高階英特爾處理器的系統晶片組),就曾推出一系列採用 80386 與 80486 的多處理器產品線,但這些所費不貲的專屬方案,仰賴特製系統晶片組與特化過的作業系統,才能正確的將系統中斷(System Interupt)傳送到各處理器,多處理器 x86 平台仍缺標準化的中斷處理機制,並非可長可久的解決之道。

1993 年 10 月 27 日,也是初代 Pentium 發表後的半年,英特爾首度公開第一版「多處理器規範」(MPS,Multi-Processor Specification)與最重要的「處理器本地端先進可程式化中斷控制器」(Local APIC,Local Advanced Programmable Interrupt Controller)與 I/O 專屬的 I/O APIC,取代老舊的 8259 PIC。

每個 Pentium 或 80486 處理器起碼要有一個 Local APIC,與系統 I/O 晶片組的 I/O APIC,透過獨立於系統匯流排的 3 位元 APIC Bus,I/O APIC 將周邊裝置的中斷需求傳遞給處理器的 Local APIC,以決定中斷服務需求該指派給那些處理器,踏出了低成本多 x86 處理器系統的第一步。

英特爾的競爭對手並非沒有替代方案,1996 年上市的 Cyrix 6×86 依據 OpenPIC 規範,支援自家定義的 SLiC,但也只有 VIA 的 Apollo 晶片組對應此規格,基本上「有跟沒有一樣」,而 AMD 的 x86 多處理器環境,更是要等到 1999 年採用 Alpha EV6 匯流排、理論上最多支援 14 顆處理器的 K7 了。

不過初代 Pentium 並未內建 Local APIC,同時期系統晶片組也沒有 I/O APIC,要打造多顆 Pentium 平台,每一顆 Pentium 需外掛一顆單價高達 26 美元、兼具 Local APIC 與 I/O APIC 兩者功能的 82498DX,I/O 也需動用一顆。換句話說,雙處理器系統就需要用到 3 顆,怎麼看都不算便宜,還會占用不少主機板空間。

後來 0.5um 製程的第二代 Pentium(P54C)變成史上第一顆整合 Local APIC 的 x86 處理器,對應的系統晶片組也陸續在南橋(South Bridge)內建 I/O APIC(未內建者,可選配專用的 82093AA I/O APIC),總算讓雙 Pentium 搖身一變,成為「Glueless」的多處理器平台。

受制於缺陷重重的系統架構,如效率不足的系統匯流排、處理器缺乏非循序記憶體存取能力、處理器共享外部的第二階快取記憶體讓匯流排問題更加雪上加霜等等,並未讓 Pentium 在伺服器市場取得重大突破,到了 Pentium Pro 面世後才迎刃而解,開啟 Xeon 統治伺服器市場之路,那又是另一段截然不同的故事了。

決定處理器核心/執行緒上限的 Local APIC 與闖禍的作業系統支援性

處理器核心持續激增的今日,Local APIC 最重要的角色在於決定處理器的核心與執行緒上限。原先最早的 APIC 上限是 15,2000 年 Pentium 4 開始出現的 xAPIC(將 APIC 的 3 位元專屬匯流排直接「融入」系統匯流排的通訊協定,避免 APIC 運作時影響記憶體存取效能)增加到 255,2008 年 Nehalem 的 x2APIC 更多達 4294967295,可視為「無限大」。

假如各位想多學些對親朋好友炫耀的「無用知識」,稍微花點腦筋,牢記一下這 3 個數字的由來:

  • APIC:Pentium 和 Pentium Pro(與 Pentium II、Pentium III、P6 核心的 Xeon)動用 Local APIC 的 ID 暫存器 24-27 四個位元,16 進位的 0xF(10 進位制的 15)用做廣播,所以 24−1=15。
  • xAPIC:Pentium 4 到 Penryn 用到 Local APIC 的 ID 暫存器 24-31 八個位元,16 進位的 0xFF(10 進位制的 255)用做廣播,所以 28−1=255。
  • x2APIC:Nehalem 開始使用存於 MSR(Model-Specific Register)的 32 位元 x2APIC ID,16 進位的 0xFFFFFFFF(10 進位制的 4294967295)用做廣播,所以 232−1=4294967295。

但帳面上的「理論值」讓人看得很爽是一回事,微軟這些作業系統廠商是否乖乖買單又是另一回事。很不幸的,AMD Zen2 世代 EPYC 與 Threadripper 將單顆處理器的實體核心術/邏輯處理器,一舉推進到 64 核/128 緒,就變成微軟 Windows 的災難了。

一台 2 顆 EPYC 7742 或 7702 的伺服器,擁有 128 個處理器核心和 256 條執行緒,但是 Windows Server 2016 和 2012 R2 並不支援「AMD 新型平台的 x2APIC」,無法吃下這麼多邏輯處理器。

事實上,根據微軟的 EPYC 性能調校文件,Windows Server 2019 之前的舊版 Windows Server,只能支援 2 顆 48 核心的 EPYC 和 192 個邏輯處理器。安裝 2019 年 9 月前的 Windows Server 2019,也需要事先在 BIOS 關閉 x2APIC 和多執行緒,安裝完畢並裝完所有的系統更新檔,重新開機進 BIOS 恢復功能,才能在工作管理員的效能選單看到全部 CPU。

再次同場加映 AMD。剛好前陣子 Anandtech 有特別報導的 EPYC 在 Windows 10 發生的災情(儘管這和 APIC 沒有關係)。Windows 系統核心會預設 64 個 CPU 組成一個「Windows Processor Group」,當邏輯處理器超過 64 個,會將多出餘數包成另一群,像一顆 64 核/128 緒的 Threadripper,就會變成一顆實體 CPU 有「兩包」64 個邏輯處理器。

但 Windows 10 要企業版(Enterprise)才提供此功能,家用版(Home)和專業版(Professional)會將「滿出來的部分」,誤判為占用另一個處理器腳位的實體 CPU,意思就是誤解成「兩顆」處理器的系統,將誤導作業系統的執行緒排程,降低系統效能,這時關掉多執行緒,很可能表現還比較好。

在計算機的世界,任何「看起來很棒」的技術和功能,無不是「軟硬兼備」的成果,當入手頂規的硬體時,也請多多關心手上的軟體環境是否可發揮最高效益。筆者現在都可以猜到花大錢買 TR 3990X 的「長輩」急著升級 Windows 10 企業版的畫面了。

「讓人比較有感」的指令集擴張

如果能讓筆者選擇,其實 x86 指令集擴張史中最重要的一幕,絕對是邁向 32 位元的 80386、虛擬 86 模式(Virtual 8086 Mode)與具備分頁表的虛擬記憶體。但事隔多年,印象最深刻的,依舊對個人電腦市場規模爆炸式成長、使用者急速增加的 1990 年代,英特爾在 Pentium 家族幹的一堆好事,包括極具歷史意義的第一次 SIMD 擴張:MMX。

相信各位不可能不知道 CPUID 這經常用來辨識處理器廠牌、功能、版本與規格的好工具,但你們知道背後作這件事的「CPUID」指令,就是從 Pentium 開始登場的嗎?眾多程式設計師計算指令執行週期數的 RDTSC(Read Time-Stamp Counter),也伴隨著 Pentium 而生。用在作業系統避免不同執行緒同時對共用資源讀寫「互斥鎖」的 CMPXCHG8B(Compare and Exchange 8 Bytes),也是小有名氣,Windows XP 就是因這個必備指令,無法執行於 Pentium 之前的所有 x86 處理器。

前面有提到 MSR(Model-Specific Register),意指在 x86 架構處理器中,一系列用於控制處理器執行、功能開關、除錯、追蹤程式執行、監測處理器效能等功能的暫存器。MSR 的雛形始於 80386 和 80486,到了 Pentium,英特爾新增 RDMSR(Read MSR)和 WRMSR(Write MSR)指令用於讀寫 MSR,使其真正的實用化。此外,軟體可透過前述的 CPUID 指令,查詢處理器可支援的功能,並確認這些功能對應的 MSR 是否存在。

同時英特爾在 Pentium「復刻」筆電專用的 80386SL 和 80486SL 處理器,那獨立於真實模式和保護模式,幹了哪些好事,連作業系統都不知情的系統管理模式(SMM,System Management Mode)。英特爾制定 SMM 的初衷,在於讓筆電 OEM 廠商自訂必備的電源管理與周邊裝置管理,如為了省電,動態關閉用不到的周邊設備,需要時再重新啟動等,將 SMM 從「特殊武器」提拔成「制式裝備」,暗示英特爾認定筆電即將普及化的未來。

順道一題,相對於 x86 指令集在節區定址定義 4 層權限的 Ring 0 到 Ring 3(數字越小權力越大),SMM 的權限經常戲稱為 Ring -2,那 Ring -1 跑到哪去了?答案是 x86 硬體虛擬化技術用來攔截「在使用者模式仍會更動系統底層的危險指令」的 Hypervisor 權限。

這些指令集擴張看似微不足道,遠不如那票 SIMD(MMX、SSE、SSE2、SSE3、SSE4、AVX、AVX-512)「華麗壯大」,卻也是不可或缺的基本功,同為「高效能處理器不可被分割的一部分」。但 Pentium 史上最知名的指令集 MMX,就是一場歡樂異常的連續劇了。

為了 MMX 讓 Pentium 被迫大興土木、脫胎換骨

Windows 95 帶動個人電腦的多媒體需求,英特爾自然不能免俗,勢必要讓處理器跟「多媒體」沾上邊,試圖推動主機板加掛一顆來自第三方的數位訊號處理器(DSP),專門處理即時性影音應用程式。但因為 NSP 的運作模式是獨立於作業系統的化外之民,等於需要作業系統開後門,微軟為此拒絕買單,因此胎死腹中,才出現了 MMX。

打從英特爾在 1996 年,拋出 MMX 這從未講清楚說明白的「無意義技術行銷商標」,全名一直眾說紛紜、莫衷一是,還先後出現 3 個版本:

  • MultiMedia eXtension
  • Multiple Math eXtension
  • Matrix Math eXtension

名稱怎樣不重要,各位只要記得一件事:為了作業系統相容性,MMX 指令集借用 x87 浮點運算暫存器(80 位元中的 64 位元)的 SIMD「整數」運算,這樣就夠了。英特爾定義全新 SIMD 暫存器,從 Pentium III「Kaimai」的 SSE(KNI,Katmai New Instructions)才開始。

指令集的編碼空間畢竟有限,英特爾要從哪裡擠出這 57 個指令的位置?英特爾將腦袋動到「0Fh」開頭的運算碼(Opcode),這卻造成前所未見的麻煩:過去 0Fh 的主要用途「當處理器的解碼器收到時,自動將該指令執行流程跳到外掛的輔助處理器」,當初英特爾就靠這招來處理 8087 浮點輔助處理器,0Fh 開頭的 x86 指令都不是什麼「需要追求效率」者,也因此,Pentium 的指令解碼器也沒有特別「關照」它們,意味著難以迅速完成解碼 MMX 指令的重責大任。

主導 Pentium MMX(P55C)研發的以色列海法團隊,不得不大興土木,將指令管線深度從五階延長到六階,爭取足夠的指令解碼時間。多這一階並非有害無益,因為執行單元將有更充裕的時間存取資料快取,並縮短電路的關鍵路徑,利於提高時脈,讓 Pentium MMX 最終可到達 300MHz,比前代 P54C 多出整整 50%。

但延長指令管線也帶來更嚴重的分支預測錯誤代價,英特爾索性將「管線深度長達 12 階」的第六世代 Pentium Pro 搭載的雙層動態分支預測與副程式返回位址緩衝區等先進技術,原封不動的逆向移植到 Pentium MMX,亦倍增快取記憶體和資料寫回緩衝區,轉換虛擬和實體記憶體位址的 TLB,也強化為可同時處理兩種不同分頁大小的版本,種種改進項目彷彿威而剛,讓吃下藍色小藥丸的 Pentium MMX 搖身一變成「5.5 代」x86 處理器。

為了減少耗電與發熱,英特爾將 MMX 執行單元與實體暫存器獨立於 x87 浮點運算器,執行 MMX 指令時,因指令集定義「邏輯上 MMX 和 x87 浮點無法同時執行」,可關閉「吃電如喝水」般的浮點單元以節約電力,可是結合加倍的快取記憶體和種種增強方案,P55C 電晶體數從 P54C 的 330 萬激增到 450 萬,製程從「不計多出 10% 晶片面積以追求最高時脈」的 350 奈米 BiCMOS 改進為「自此英特爾轉向追求更低成本並降低耗電」的 280 奈米 CMOS,晶片面積和製造成本仍足足比 P54C 多 50%。

Pentium MMX 在 1997 年 1 月上市沒多久,同年 5 月同樣支援 MMX 的 Pentium II 就以「塑膠大彈夾」外觀,現身於各地電腦賣場的玻璃櫃,無論怎麼看,Pentium MMX 都是過渡期強烈的尷尬產物。

(Source:Flickr/Andy Rogers CC BY 2.0)

但 Pentium MMX 對英特爾在以色列海法的研發團隊而言,卻是極為重要的歷史里程碑,建立起「擅長精鍊現有架構壓榨更多價值」的名號,接連重塑 P6 微架構成為 Centrino 心臟的 Pentium M(Banias, Dothan)、當英特爾在 Pentium 4(NetBurst)慘遭滑鐵盧的危急存亡之秋端出 Core 2(Merom, Conroe, Woodcrest)救駕成功、融合 P6 與 NetBurst 之長的 Sandy Bridge 終結 AMD K8 的輝煌歲月、直到「奮六世之餘烈」集大成的「終極 x86 微架構」Skylake,清一色都是出自以色列海法團隊的不朽傑作。

x86 指令集長期欠缺標準造成競爭對手與軟體開發商的困擾

Pentium 的「歷史地位」倒是值得另外添一筆:x86 指令集欠缺公開業界標準,搞死不少人的陳年舊帳,終於正式浮上檯面。

Pentium Pro 總工程師之一的 Robert Colwell 回憶錄《The Pentium Chronicles》說過,開發一顆 x86 處理器,最艱鉅的挑戰在於「如何保證可相容所有舊程式」。特別早期 x86 處理器,很多未定義的運算碼(Opcode)並沒有遮掉,被人發現又拿來用了,以後的處理器開發人員就只能乖乖想辦法「塞」進去,前提是你也要知道這些陷阱到底藏在哪裡。

各位是否天真的以為所有 x86 處理器廠商的產品,都保證彼此相容,可執行一模一樣的軟體?很遺憾的,這種好事從來就不存在英特爾統治的 x86 世界(最起碼,前陣子讓 Linus Torvalds 大暴走的 AVX-512,AMD 現有產品也是付之闕如),英特爾在 Pentium 時代的所作所為就是最好例證,讓初版使用者手冊描述新增指令的「附錄 H」故意保持完全空白,英特爾的競爭者與軟體開發商紛紛變成倒楣的苦主。

英特爾並不像那票會定期推出版本演進與相關規範的 RISC 指令集(有關心 ARM 的讀者應該很清楚)、積極推廣自家指令集給其他潛在競爭對手,而是完全不管其他人死活。想研發 x86 相容處理器的有志之士,假若沒有跟英特爾簽訂互相授權協議,只有兩條路可選:乖乖用電子顯微鏡默默研究英特爾處理器的晶粒,嘗試逆向工程,要不然就乾脆不支援不顧相容性,碰到就視為非法指令,剩下的爛攤子就丟給作業系統廠商傷透腦筋了。

像 Cyrix 在被 National Semiconductor 購併前,壓根沒有英特爾技術授權,只能悶著頭逆向工程慢慢搞,自然也無法 100% 相容,讓號稱「第六世代」的 6×86,連 CPUID 和 RDTSC 都付之闕如,指令集相容水準只有 80486 等級,甚至還得逼迫軟體廠商撰寫修正程式。同時期的 AMD 則是不計代價拚死拚活,都要藉由逆向工程擠出 100% 相容性,下場就是產品上市延誤,錯失商機,還虧當初 Compaq 傻傻不肯推出 Pentium 個人電腦,寧願痴痴等待 AMD K5,更慘的是,撐到最後還是等不到。

英特爾競爭者也都不是省油的燈,不遑多讓搶著跳出來扮演「麻煩製造者」,自行定義「兄弟獨有之創見」的自家指令,不僅企圖爭奪 x86 指令集的主導權,並強化產品效能及行銷籌碼,像 AMD K6 的 3DNow!、AMD K8 的 x86-64,Cyrix 6x86MX 的 EMMI 與 Cyrix III 的 MMX-FP,Centaur 一度想不開的 57 個 SIMD 浮點指令和 22 個自定義浮點暫存器,都是斑斑可考的歷史陳跡。

AMD 還一度想不開,搶先註冊「SSE5」,擺明跟英特爾 AVX 打對台,還好在 2009 年 5 月 6 日緊急採煞車,宣布「皈依」AVX,但還是忍不住撈過界「補完」被英特爾廢除的四運算元指令格式(xmm1=xmm2×xmm3+m32)。AMD 要開始支援亂成一團的 AVX-512 並完全相容,大概也是很久以後的未來了。

回顧這些年來的 x86 指令集擴充戰爭,唯一從英特爾手上搶下先機的,也只有 AMD x86-64 那次,還是微軟私下威脅「不打算支援兩種不同的 64 位元 x86」(英特爾本來有自己的 Yamhill,但為了保護 IA-64 遲遲不肯拿出來)強迫英特爾接受的結果。

最初英特爾多心不甘情不願、打死都不承認 64 位元 x86 存在的「IA-32e」和 AMD x86-64 也並非一模一樣,英特爾獨占 CMPXCHG16B(Pentium 那個 CMPXCHG8B 的進階版)和 SSE3,AMD 多出分頁表 NX(No Execute)保護位元和 3DNow!。談到 x86 處理器廠商要彼此 100% 水乳交融,說有多麻煩就有多麻煩,說微軟有多火大就有多火大。

不過亂象還是持續延燒,還燒到虛擬化領域,近十多年來虛擬化應用快速普及,然後 2005 年英特爾 VT-x(Vanderpool)和 2006 年 AMD AMD-V(Pacifica),雙方根本就是各搞各的,老死不相往來,讓 VMware vMotion 此類不停機的虛擬機動態遷移技術,遲遲跨越不了不同 x86 處理器廠商的邊界,無形中也侷限了 x86 處理器「向上發展」的潛力,目睹此景,IBM 應該會繼續開心下去。

重塑 x86 處理器世界觀的歷史認知

行文至此,想必各位看官臉上已掛著顫抖的嘴角與充滿劫後餘生的表情,或多或少逐步解構並重組過往對個人電腦市場與 x86 處理器演進史的認知,有可能瞬間茅塞頓開,也有可能繼續滿頭問號。

本文並非教科書,而是經由複習坊間甚少重視的歷史背景與不容易注意到的細節,體認到平日陪伺在旁、習以為常的 x86 處理器,能走到今天是多麼不容易的一件事。羅馬不是一天造成,英特爾的霸權也不是平白從天上掉下來,這些年來我們一同擠過這麼多條牙膏,更不是毫無苦衷。

身為英特爾 1990 年代「Landmark」晶片與無數潛藏已久歷史暗流的縮影,Pentium 帶來 x86 世界太多「第一次」,承先啟後,奠定 25 年技術演進與市場發展的基礎邏輯。畢竟電腦是人類創造的東西,背後的人性和思維遠遠超越技術。英特爾可靠開創新局的 Pentium 與繼往開來的 Pentium Pro,在眾多敵人環伺下殺出一條通往全新市場血路過程中「產生的價值」,如「技術領先無法保證商業勝利」和「成功的產品往往是折衷妥協後的產物」,統統很有意思,更值得各位細細品味。

關於 Pentium,似乎筆者不小心遺漏了什麼,聽說跟浮點除法(FDIV)有關?算了,就當作提醒世人,再精密複雜的處理器,也會因臭蟲出包的教訓吧。

(首圖來源:Flickr/Sh4rp_i CC BY 2.0)原文出處: 從 Pentium 回顧 x86 處理器到底哪裡難做 痴漢水球