--- title: 現代處理器設計:原理和關鍵特徵 image: https://i.imgur.com/56Ow9zJ.png --- # [現代處理器設計](http://hackfoldr.org/cpu):原理和關鍵特徵 * ==[直播錄影(上)](https://www.youtube.com/watch?v=9hNl8fWnHLI)== * ==[直播錄影(下)](https://www.youtube.com/watch?v=UHOvgmZNjXI)== ## 導讀短片 * [How A CPU Works | A Basic Guide On Processor Stages & Functionality](https://www.youtube.com/watch?v=BREokpg5AIw) * [多核心處理器有前途嗎?](https://www.youtube.com/watch?v=yixQwNj7G0w) - [ ] 〈The Evolution Of CPU Processing Power〉系列 ==^必看^== * [Part 1: The Mechanics Of A CPU](https://youtu.be/sK-49uz3lGg) * 1971 年問世的 [Intel 4004](https://en.wikipedia.org/wiki/Intel_4004) 是世界上第一顆商用微處理器晶片,也是首個用於計算機的 4 位元微處理器,採用 10 微米製程,包含 2300 個電晶體; * 由於 4004 效能很差,[Intel 4004](https://en.wikipedia.org/wiki/Intel_4004) 市場反應十分不理想,其最高時脈有 740KHz,能執行 4 位元運算,支援 8位元指令集及 12 位元定址。[Intel 4004](https://en.wikipedia.org/wiki/Intel_4004) 原先是英特爾為一家名為 Busicom 的日本公司而設計,用來生產計算機。 * 典型 RISC pipeline (管線): Fetch :arrow_right: Decode :arrow_right: Execute * [Part 2: Rise Of The x86](https://youtu.be/kvDBJC_akyg) * 1972 年推出的 [Intel 8008](https://en.wikipedia.org/wiki/Intel_8008) 是世界上首款 8 位元微處理器,有兩種工作時脈: 0.5 MHz 及 0.8 MHz,雖然比 [Intel 4004](https://en.wikipedia.org/wiki/Intel_4004) 的工作時脈低,但得益於 8 位元處理器的設計,整體效能仍比 4004 好上許多。 * 8008 可支援到 16KB 的記憶體,由 4000 個電晶體構成,指令集數量僅 20 餘個,基本指令周期為 20~50μs。8008 有 18 個引腳,其中位址匯流排使用 14 個引腳,並與 8 位元資料匯流排共用引腳。 * 1974 年,Intel 發布 8080 微處理器,公認是第一款真正可用的微處理器。8080 的晶片封裝採用 40 個引腳,其中 8 個資料匯流排引腳、16 個位址匯流排引腳都是專用的,因此資料匯流排與位址匯流排可並列工作。8080 的擴充後的指令集在原始碼級別上向前相容 8008 指令集。 * 1978 年推出的 [8086](https://en.wikipedia.org/wiki/Intel_8086) 為 Intel x86 家族的開端,也是首顆 16 位元微處理器晶片,跟之前 Intel 推出的微處理器都不相容。其記憶體定位功能達到 1 MiB * 8086 包含 2.9 萬個電晶體,工作時脈為 4.77MHz,而 8086-2 是 8 MHz,還生產出搭配的數學輔助運算晶片 (math co-processor) 8087 來處理浮點運算。現代的處理器絕大多數內建數學輔助運算單元,因此不再需要額外的數學輔助運算晶片,但受限於 1970 年代的技術,只能將數學輔助運算器做成另外一個晶片,供用戶選擇。好處是減少製造的成本和提高良率,也增加對運算需求不大的客戶在採購上的彈性。 * 中斷處理, CISC, IBM PC, Microsoft DOS * [Part 3: The Origin Of Modern Operating Systems](https://youtu.be/NTLwMgak3Fk) * [multiprogramming](https://en.wikipedia.org/wiki/Computer_multitasking#Multiprogramming), 分時多工, MMU, 開機流程 (以 Linux 核心為例), 虛擬記憶體, register file / GPR * [13:08](https://youtu.be/NTLwMgak3Fk?t=788)-[14:44](https://youtu.be/NTLwMgak3Fk?t=824): [Microsoft Windows 1.0](https://en.wikipedia.org/wiki/Windows_1.0) 執行畫面 * 真實模式 / 保護模式: 任務 (task,或譯為「工作」) 代表一個執行的程式,可能是個應用程式、作業系統服務、中斷/例外處理常式、或是作業系統的核心。即使是最簡單的系統,至少也會有一個任務 (如 [system idle process](https://en.wikipedia.org/wiki/System_Idle_Process)) * 在保護模式中,一個工作是由「工作執行空間(task execution space)」和「工作狀態分段(task-state segment, TSS)」所構成: * 工作執行空間是個工作的活動空間,由一個 code segment、一個 stack segment、和一個或多個 data segment 組成。如果作業系統有使用保護機制的話,每個工作還要為各個特權等級分別準備一個分開的堆疊 segment。而 TSS 則是指出工作執行空間的位址,並存放工作的狀態。如果系統中有多個工作的話,它還負責把各個工作連結起來。 * TSS 是一個系統 segment,因此它有自己的 segment descriptor。在系統中,就是利用它的 segment selector 來表示一個工作 TSS。在載入一個工作執行時,TSS 的 segment selector、基底位址、邊界值、和一些 segment descriptor 的屬性,都會載入到工作暫存器(task register,TR)中。同時,若有使用分頁功能,則在工作切換時,還會把該工作的分頁目錄的基底位址載入到 CR3 中。 * 延伸閱讀: [cpu-internals](https://github.com/LordNoteworthy/cpu-internals): 針對 Intel/AMD x86 系列 ## 背景知識 * [How shit works: the CPU](https://www.slideshare.net/holograph/how-shit-works-the-cpu) * [Why are we still using CPUs instead of GPUs?](https://superuser.com/questions/308771/why-are-we-still-using-cpus-instead-of-gpus) * [計算機組織結構的背景知識](https://hackmd.io/@sysprog/H1sZHv4R) ## [Modern Microprocessors (A 90-Minute Guide!)](http://www.lighterra.com/papers/modernmicroprocessors/) 重點提示和解說 ### More Than Just Megahertz 以一份簡潔的分數測試報告來說明時脈不代表效能,真正的效率取決於「在一個 clock cycle 中能做多少事情」。 ### Pipelining & Instruction-Level Parallelism ![](https://i.imgur.com/K3937kr.png) 將 CPU 處理資料的部份分為數個 stage,藉由這種設計可以讓處理的效率更好,因為每個 stage 在任意時間都有在工作。 ![](https://i.imgur.com/UOiN7u7.png) 而各個 stage 間是利用 latch 來傳遞資料給下一個 stage。 ![](https://i.imgur.com/3NZP5tp.png) 除此之外,因為資料的相依性,所以會從 Execute 或 Writeback 之後拉一條線到 Execute 之前,這樣可以減少不必要的等待。 ![](https://i.imgur.com/jk7xir5.png) 再來就是,Execute 階段中其實是由好幾組邏輯運算單元組成。 ![](https://i.imgur.com/96AFVpk.png) RISC 相較複雜指令集較容易實現 pipeline,也因此,一個較低時脈的 RISC 處理器,可能會較時脈高但指令集較複雜的處理器效能好。 ### Multiple Issue – Superscalar 如同上一部份所說,Execute 中有好幾組邏輯運算單元,一條指令中不一定都會用到全部的運算單元,所以我們可以多讀取指令,依照所需分配到不同路徑。 ![](https://i.imgur.com/95XkjlU.png) 利用上面 pipeline 的圖案呈現出來就像這樣 ![](https://i.imgur.com/TxuChIk.png) 概念呈現影片: * [Pipelining & Superscalar (+ Hazard)](https://www.youtube.com/watch?v=JkwDt0tj1fs) * [Introduction to VLIW](https://www.youtube.com/watch?v=b8UBpmPBbns) ### Explicit Parallelism – VLIW VLIW 也就是 **Very Long Instruction Word**。表示一條指令中可以執行多種操作。 ![](https://i.imgur.com/wrZQ7wl.png) 好處是不用檢查資料相依性。壞處是遇到 cache miss 的話,處理器就要在那邊空等。 ### Instruction Dependencies & Latencies 既然 pipeline 可以讓 CPU 使用地更有效率,那我們只要把 stage 跟的更細,效能就會變得更好?從文章中得到的答案是否定的。 較深的 pipeline 的 latency 會較高,也因此不會比較短的 pipeline 效率較高,因為 instruction dependency 導致 pipeline 階段中不得不填上 bubble。memory load 的 latency 也是十分麻煩,因為通常在程式一開始就會載入記憶體,此時僅有較少的指令可以填入空檔,而且 load latency 也取決於資料是否存在 cache。 在文中的例子,Line 2 必須等待 Line 1 運算的結果,如果 stage 很多,就表示所需要等待的時間可能會更長。 ```cpp a = b * c; d = a + 1; ``` 而當一個指令從 Execute 階段到運算的資料能夠被其他指令使用,其中所經過的時間就叫 Instruction Latency。 ### Branches & Branch Prediction 當遇到 branch instruction 時,CPU 不知道到底要抓下一個指令,還是跳躍後的那一個指令,所以 CPU 只能去預測。 預測的方法分為兩種,第一種是靜態的預測,也就交給 compiler 來做個 mark。第二種則是由 CPU 來根據過往的紀錄來進行預測。 文中也以 Pentium Pro/II/III 舉例,再怎麼聰明的 predictor 也只有 90% 的正確率,而預測失敗時會損失 30% 的效能。換句話說,Pentium Pro/II/III 有三分之一的時間根本沒做到什麼事,只在那邊說「糟糕~ 錯了」。 為了避免 branch instruction,所以有些平台會提供 conditional move instruction。 ### Instruction Scheduling, Register Renaming & OOO 這一個區段主要的概念就是重新排序指令避免各種 hazard 跟 latency。 根據重新排序的方式可以分為靜態與動態。 * 靜態:讓 compiler 來重新排序。 * 動態:在 runtime 時,由 CPU 來往後看幾個指令,進行排序。又稱為 Out-of-Order (OOO),優點是有些指令在 runtime 時才能夠更準確的去排,但缺點是 OOO 所需要的功耗較高。 ### The Power Wall & The ILP Wall * The Power Wall 當增加 20% 的 clock speed,功耗就增加更多,如 50%。 為了提升 clock speed 而必須要提供更多的電力以及改善散熱能力,並不那麼簡單,所以在這邊就遇到了一個瓶頸,稱之為 The Power Wall。 * The ILP Wall 因為程式中的 load latency、cache miss、branch prediction 等問題,讓程式沒辦法有效的平行化,導致所謂的 The ILP Wall。 ### What About x86? 在現今的 x86 架構中,會把原先的 x86 指令轉換成類似 RISC 的指令才進行處理。那些類 RISC 的指令又稱為 micro ops (也寫作 μops) ![](https://i.imgur.com/d68hr7W.png) ### Threads – SMT, Hyper-Threading & Multi-Core * SMT (Simultaneous Multi-Threading) 在傳統的狀況下,當有 thread 空閒時,會切換至其他 thread,但如此一來,資源就無法更有效的被利用。 ![](https://i.imgur.com/6xZz6PX.png) 而 SMT 簡單來說就是在 CPU 中同時執行兩個 thread。等於把 thread level parallelism 轉換成 instruction level parallelism。 ![](https://i.imgur.com/etGQBLK.png) * Multi-Core 將多個核心封裝至一個 chip 中。 ### More Cores or Wider Cores? SMT 的核心通常會比較大一點,在相同大小的情況下,可以放入較多的普通核心。 那哪一種會比較好?端看程式的用途。 * 普通核心:適合比較活躍但受限於 memory latency 的應用程式,如資料庫系統、3D 圖形渲染等。因為 SMT 核心會花較多的時間在等待記憶體上。 * SMT 核心:對於大部分的應用程式都適用,因為單執行緒的效能會比較重要。 #### Memory & The Memory Wall 為了從記憶體當中載入資料,會花費許多時間,導致效能受到限制,這種情況就叫 The Memory Wall。 所以在現今的設計中都會使用 cache 來進行快取。 #### Caches & The Memory Hierarchy 由於中央處理器的效能進展迅速,記憶體經常會有跟不上的情形。為提昇中央處理器的效率,從 i486 開始,所有 Intel 設計的中央處理器都有內建一定大小的 cache memory 來增進記憶體讀取的效率。 cache 的原理是利用一小塊高速記憶體來存放最近使用過的的程式碼或資料,如此一來,這些資料在重覆使用時就不必每次都到系統記憶體上拿。由於程式經常會重覆存取在同一區域內的記憶體資料 (及 locality),因此 cache 對系統效率的提昇有相當大的幫助。 換言之,cache 是個緊鄰處理器,速度快但容量小的記憶體。因為容量小,一定不可能把需要的資料都快取起來,所以採用階層式的概念來快取資料。 如 L1 cache, L2 cache, L3 cache,速度由快到慢,大小由小到大。 這樣聽起來好像很有道理,但是其中有一個小問題:雖然個人電腦的架構在設計上是把記憶體位址和輸出入位址分開,但是在很多情況下我們仍有記憶體對映輸出入 (memory-mapped IO; MMIO) 的需求,例如螢幕顯示的控制,以標準的 VGA 卡來說, 當我們在對 0A0000h 到 0BFFFFh 這一段記憶體進行操作時,實際上資料是送到顯示卡去,而不是到系統記憶體上。為了維持這類記憶體對映輸出入的正常運作,這些記憶體位址是不能夠被 cache 處理的。那麼中央處理器怎麼知道哪些記憶體位址可以快取而哪些不行呢? 這個問題通常是由軟體和硬體一起合作解決的;中央處理器對軟體控制 cache 的能力都有不同的規範。 另外,根據不同的設計,可以分為 direct-mapped cache、N-way set associative cache。 > 以下改寫自 [淺談memory cache « EngineC's Blog](http://opass.logdown.com/posts/249025-discussion-on-memory-cache) 當初記憶體為什麼要像現在設計成階層式的架構?很簡單,因為我們發現我們寫的程式在存取記憶體的過程中,有兩大現象: * 剛剛用過的記憶體很容易再被使用(例如,for迴圈) * 如果一個記憶體剛剛使用過,他附近的記憶體位址也很可能被使用到(例如,陣列存取) 這就產生了設計記憶體架構的兩大原則,Temporal Locality 和 Spatial Locality。另外還有記憶體本身的速度、成本的考量,越快的記憶體越貴,我們決定設計出階層式的記憶體架構。 ![](https://i.imgur.com/nDn3vfH.png) 越上層的記憶體越貴,速度越快,容量越小。越下層記憶體越便宜,速度慢,但容量越大。資料只會在相鄰兩層間移動。 我們把資料一次從記憶體下層轉移到記體上層的單位定作 block。若處理器要求讀取某個 block 的資料,剛好在上層的記憶體內,那就稱為 hit。反之,如果不在上層,那就稱為 miss。hit rate 就是你成功在上層記憶體就找到你要的資料的次數比例,相反就是 miss rate,當然 hit rate + miss rate = 1。現今電腦的 hit rate 可達到驚人的 95% 以上。 另一組對電腦效能來說影響重大的因素: * hit time: 判斷記憶體是否 hit + 把上層資料搬到處理器的時間 * miss penalty: 把下層記憶體的資料搬到上層 + 上層記憶體資料搬到處理器的時間 cache 在設計時,要先回答一個重要的問題: > 處理器怎麼知道資料是否在 cache 中,並正確從 cache 抓出想要的資料? ## 延伸閱讀 * [Scalar Performance Optimization](https://www.rsmas.miami.edu/users/miskandarani/Talks/CCS/scalopt.pdf) * [Introduction to High Performance Scientific Computing](http://pages.tacc.utexas.edu/~eijkhout/Articles/EijkhoutIntroToHPC.pdf)