JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

從指令到管線:處理器如何真正執行程式

你寫下一行程式碼;十億個電子之後,正確答案蹦了出來。可是中間究竟發生了什麼?本階段為架構學習軌道揭開序幕,從你已經熟悉的[[transistor|電晶體]]一路往上爬,爬到軟體與矽相遇的那一層——[[ic-instruction-set-architecture|指令集]]——再帶著一道謙卑的 ADD 指令走過「擷取—解碼—執行」的迴圈,並說明[[ic-instruction-pipeline|管線]]如何把工作重疊起來、一次跑好幾道。把這台基礎機器弄對,後面每一個階段——快取、平行化、加速器——都只是更聰明地餵養它的方式。

抽象之塔:從一顆電晶體到一個念頭

把一支現代手機握在手裡,你握著的是人類造過最層層疊疊的東西之一。最底層是電晶體——一個微觀的開關,不過是一個電壓把通道打開或關上。把幾顆按正確的圖樣接在一起,你就得到一個邏輯閘,能算出一個位元的布林邏輯:AND、OR、NOT。把邏輯閘疊成加法器與暫存器,再疊成一顆處理器,把幾百億顆蝕刻在一塊積體電路上——在那座塔的某一層,電荷的原始物理開始表現得像*算術*,然後像*程式碼*,再然後像一通與你阿嬤的視訊。電腦架構,研究的正是那座塔裡其中一層至關重要的樓層。

為什麼那一層樓值得擁有自己的學科?因為它是兩個龐大世界必須相遇的樓層。在它之下,住著電路與晶片工程師,他們以電壓、閘延遲與矽面積來思考。在它之上,住著程式設計師,他們以變數、迴圈與函式來思考。兩邊都不想知道對方的細節——一個 JavaScript 開發者永遠不該需要在意一顆電晶體如何切換,而一個晶片設計者也該能徹底重建矽,卻不弄壞任何一支現有的程式。架構,就是那個讓兩件事同時成立的外交層。

ISA:一份沒人被允許違背的合約

你要怎麼讓兩個世界相遇,又不逼任何一方知道另一方?你寫一份合約。在運算裡,那份合約就是指令集架構,簡稱 ISA。它是一個精確、公開的承諾,內容是:*這顆處理器看得懂的指令完整清單在此,每一道究竟做什麼、有哪些暫存器、數字在記憶體裡如何排列。* x86、ARM 與 RISC-V 就是其中最有名的幾個。ISA 刻意只描述行為,從不描述構造——它告訴你機器做*什麼*,卻擺明拒絕說*怎麼做*。

那一份拒絕,正是全部的天才所在。因為 ISA 只凍結了行為,Intel 與 AMD 才能造出南轅北轍、卻都能跑 x86 的晶片;Apple 才能一代又一代地重新設計它的 ARM 核心、把效能翻倍,而你那些幾年前就編譯好的應用程式,原封不動地照跑。合約是雙方唯一達成共識的東西,而它讓底下的硬體能被無止境地重新發明。一道指令本身不過是一個二進位數字,一串晶片去解碼的位元樣式——是那部布林機器詮釋一段碼的一小片。

  C source           one ISA instruction        the actual bits
  ----------         -------------------        ---------------
  z = x + y;   --->   ADD  R3, R1, R2     --->   0000000 00010 00001 000 00011 0110011
                      "add R1 and R2,            (a 32-bit RISC-V word the
                       put result in R3"          decoder will pick apart)

  The ISA is the middle column: a fixed vocabulary the compiler
  targets and the hardware promises to obey. Change the silicon
  freely -- just keep honouring this column.
ISA 坐落在你所寫的語言與晶片所跑的位元之間。編譯器把你的程式碼變成 ISA 指令;硬體再把那些指令變成電壓——而 ISA 是唯一不變的支點。

擷取、解碼、執行:每顆 CPU 的心跳

把一顆處理器剝到只剩靈魂,你會找到一個簡單到連小孩都跟得上的迴圈,每秒重複數十億次。CPU 持有一個特別的指標——程式計數器(program counter)——存著下一道指令的記憶體位址。然後它永遠做三件事:擷取(fetch)那個位址上的指令、解碼(decode)它好弄清楚它命令什麼、再執行(execute)它——做算術、碰記憶體,或跳到別處去。接著它把程式計數器往前推,再從頭來一遍。這就是「擷取—解碼—執行」迴圈,是運算字面意義上的心跳。

用我們的 ADD 把它變得具體。那道指令以二進位字的形式住在記憶體裡;處理器的資料路徑(datapath)則是那張由導線、邏輯閘與儲存元件組成的網路,負責把它運過那三個步驟。它賴以運算的高速便箋簿,就是暫存器檔(register file)——一小排暫存器,每個存一個字,存取起來遠比主記憶體快。經典的教學設計把這個迴圈切成五個整齊的階段,而追著 ADD 走過它們,是看懂一顆 CPU「思考」最清晰的方式。

  1. IF——指令擷取。程式計數器指著我們的 ADD;處理器把那個 32 位元的字從指令記憶體裡讀出來,並把計數器加一,指向下一道指令。
  2. ID——指令解碼。解碼邏輯把位元樣式當成布林欄位來讀:這是一道 ADD、來源是暫存器 R1 與 R2、目的地是 R3。那兩個來源暫存器同時被從暫存器檔裡讀出來。
  3. EX——執行。那兩個值流進算術邏輯單元(ALU)——一塊由邏輯閘搭成的加法器——把它們相加、產生總和。這裡,才是真正運算發生的地方。
  4. MEM——記憶體存取。一道單純的 ADD 不碰記憶體,所以它在這一階空轉。(一道載入或儲存指令會在這裡讀寫資料記憶體——這正是這一階存在的理由。)
  5. WB——寫回。總和被寫回暫存器檔裡的目的暫存器 R3。指令完成;結果現在對任何接下來需要 R3 的指令都可見了。

管線:一間給指令用的自助洗衣店

一次只跑一道指令,你幾乎浪費了一切。當 ALU 在 EX 階段忙著時,擷取指令的硬體乾坐著;當 WB 在寫回結果時,ALU 在打盹。這就像洗衣服時,先洗一桶、完全烘乾、摺好,然後*才*開始下一桶——烘衣機運轉的整段時間,洗衣機都冰冷地閒著。沒人這樣洗衣服。你會在第一桶一移到烘衣機的瞬間,就開始洗第二桶。每台機器都保持忙碌,一疊照順序洗會花上一整天的衣服,只用一小段時間就洗完。

這正是指令管線。把資料路徑切成那五個階段,在每一對之間塞一個小小的暫存器去存住進行中的指令,讓五個階段同時運轉——各自處理*不同*的指令。當 ADD 進入 EX,它後面那道指令進入 ID,而一道全新的進入 IF。指令們步調一致地行進過各階段,每個時脈節拍走一步,就像生產線上的車子。沒有任何單一的 ADD 跑得更快——它仍要五個階段——但現在一道完成的指令*每一個週期就從末端滾出來一道*,而不是每五個週期一道。

  Cycle:      1     2     3     4     5     6     7     8     9
            +-----------------------------------------------------+
  ADD   R3  | IF | ID | EX |MEM | WB |                            |
  SUB   R6  |    | IF | ID | EX |MEM | WB |                       |
  AND   R8  |    |    | IF | ID | EX |MEM | WB |                  |
  OR    R9  |    |    |    | IF | ID | EX |MEM | WB |             |
  XOR  R10  |    |    |    |    | IF | ID | EX |MEM | WB |        |
            +-----------------------------------------------------+
            ^                 ^
          fill-up         steady state: ONE instruction finishes
          (latency)       every clock cycle (throughput)

  5 instructions, no pipeline:  5 x 5 = 25 cycles
  5 instructions, pipelined:    9 cycles   (~2.8x faster, and
                                 the gap only grows with more)
五道指令在五階管線裡重疊。管線填滿之後,每個週期就冒出一個結果——同樣的硬體、同樣的每指令延遲,吞吐量卻可達約五倍。

為什麼速度是兩個數字、不是一個:頻率 × IPC

問一個初學者為什麼一顆晶片比另一顆快,答案通常是單一個數字:gigahertz(吉赫)。那只是一半的真相,而缺的那一半,正是現代設計安身立命之處。整條管線跟著一個鼓點行進——時脈訊號,一個以固定頻率上下擺動的方波。每一個節拍,每道指令前進一階。一個 3 GHz 的時脈每秒跳三十億次,所以它的週期只有三分之一奈秒。時脈越快、每秒節拍越多、推過的指令越多。到這裡,都還是 gigahertz 的故事。

但還有第二根槓桿,同樣強大:你*每*一個節拍完成多少道指令。我們這條簡單的管線最好也只能每週期一道——IPC(每週期指令數)等於 1。一顆聰明的超純量(superscalar)處理器每個週期擷取並執行好幾道指令,IPC 達到 4 甚至更高。真正的效能是這兩者的乘積,而一個著名的恆等式把它釘死:程式執行時間 = 指令數 × 每指令週期數 × 每週期時間。晶片設計者可以對這三項全部施力。

      Execution time  =  N_instructions  x  CPI  x  T_clock
                      =  N_instructions  x  CPI  / f_clock

      where  CPI = cycles per instruction (= 1 / IPC)
             f_clock = clock frequency

  Worked example -- a program of 1 billion instructions:

    Chip A: f = 4 GHz, IPC = 1     -> time = 1e9 x 1   / 4e9 = 0.250 s
    Chip B: f = 3 GHz, IPC = 2     -> time = 1e9 x 0.5 / 3e9 = 0.167 s

  Chip B is SLOWER in gigahertz yet 1.5x FASTER overall --
  because it does more work per tick. Frequency is only half
  the story; IPC is the other half.
效能方程式。頻率較低的晶片反而勝出,因為更高的 IPC 補償有餘——這正是為什麼 gigahertz 的競賽,悄悄讓位給了 IPC 的競賽。

這就是為什麼,大約在 2005 年,時脈速度停止了它數十年來那樣的攀升。把頻率催得更高,燒掉功率與熱量的速度,比晶片散得掉的還快——這道牆,後面每一個階段都會回頭再談。於是設計者轉向另一根槓桿:每週期做更多工、更多核心、更聰明的管線、更深的指令級平行性。這些把戲每一個,骨子裡都是一種在不把晶片燒熔的前提下提高 IPC 的方法。你現在已經見過的這台基礎機器——ISA、迴圈、管線、頻率、IPC——正是它們全部要去最佳化的對象。

基礎機器,與前方的路

退一步,看看你現在握有什麼。你能把一行程式碼順著塔往下追:從一個敘述,到一道 ISA 指令,到一個二進位字,到電子在邏輯閘裡翻動電晶體。你能帶著那道指令走過擷取—解碼—執行,看它漣漪般掃過五個資料路徑階段,讀寫暫存器檔,並理解為什麼把那些階段在管線裡重疊起來會把吞吐量倍增。而你能用正確的雙手握法去推理速度:時脈頻率與 IPC 兩者並重。這就是基礎機器——那台簡單、誠實的 CPU,每一個華麗的最佳化都被栓在它身上。