計算機組成原理——入門篇


馮·諾依曼體系結構

計算機的基本硬件組成

CPU

CPU是計算機最重要的核心配件,全名你肯定知道,叫中央處理器(Central Processing Unit)。計算機的所有“計算”都是由 CPU 來進行的。

內存(Memory)

你撰寫的程序、打開的瀏覽器、運行的游戲,都要加載到內存里才能運行。程序讀取的數據、計算得到的結果,也都要放在內存里。內存越大,能加載的東西自然也就越多。

主板(Motherboard)

存放在內存里的程序和數據,需要被 CPU 讀取,CPU 計算完之后,還要把數據寫回到內存。然而 CPU 不能直接插到內存上,反之亦然。於是,就帶來了主板。

主板是一個有着各種各樣,有時候多達數十乃至上百個插槽的配件。我們的 CPU 要插在主板上,內存也要插在主板上。主板的芯片組(Chipset)和總線(Bus)解決了 CPU 和內存之間如何通信的問題。芯片組控制了數據傳輸的流轉,也就是數據從哪里到哪里的問題。總線則是實際數據傳輸的高速公路。因此,總線速度(Bus Speed)決定了數據能傳輸得多快。

有了三大件,只要配上電源供電,計算機差不多就可以跑起來了。但是現在還缺少各類輸入(Input)/ 輸出(Output)設備,也就是我們常說的I/O 設備。如果你用的是自己的個人電腦,那顯示器肯定必不可少,只有有了顯示器我們才能看到計算機輸出的各種圖像、文字,這也就是所謂的輸出設備。

同樣的,鼠標和鍵盤也都是必不可少的配件。

最后,你自己配的個人計算機,還要配上一個硬盤。這樣各種數據才能持久地保存下來。絕大部分人都會給自己的機器裝上一個機箱,配上風扇,解決灰塵和散熱的問題。不過機箱和風扇,算不上是計算機的必備硬件,我們拿個紙板或者外面放個電風扇,也一樣能用。

說了這么多,其實你應該有感覺了,顯示器、鼠標、鍵盤和硬盤這些東西並不是一台計算機必須的部分。你想一想,我們其實只需要有 I/O 設備,能讓我們從計算機里輸入和輸出信息,是不是就可以了?答案當然是肯定的。

你肯定去過網吧吧?不知道你注意到沒有,很多網吧的計算機就沒有硬盤,而是直接通過局域網,讀寫遠程網絡硬盤里面的數據。我們日常用的各類雲服務器,只要讓計算機能通過網絡,SSH 遠程登陸訪問就好了,因此也沒必要配顯示器、鼠標、鍵盤這些東西。這樣不僅能夠節約成本,還更方便維護。

還有一個很特殊的設備,就是顯卡(Graphics Card)。現在,使用圖形界面操作系統的計算機,無論是 Windows、Mac OS 還是 Linux,顯卡都是必不可少的。有人可能要說了,我裝機的時候沒有買顯卡,計算機一樣可以正常跑起來啊!那是因為,現在的主板都帶了內置的顯卡。如果你用計算機玩游戲,做圖形渲染或者跑深度學習應用,你多半就需要買一張單獨的顯卡,插在主板上。顯卡之所以特殊,是因為顯卡里有除了 CPU 之外的另一個“處理器”,也就是GPU(Graphics Processing Unit,圖形處理器),GPU 一樣可以做各種“計算”的工作。

鼠標、鍵盤以及硬盤,這些都是插在主板上的。作為外部 I/O 設備,它們是通過主板上的南橋(SouthBridge)芯片組,來控制和 CPU 之間的通信的。“南橋”芯片的名字很直觀,一方面,它在主板上的位置,通常在主板的“南面”。另一方面,它的作用就是作為“橋”,來連接鼠標、鍵盤以及硬盤這些外部設備和 CPU 之間的通信。

有了南橋,自然對應着也有“北橋”。是的,以前的主板上通常也有“北橋”芯片,用來作為“橋”,連接 CPU 和內存、顯卡之間的通信。不過,隨着時間的變遷,現在的主板上的“北橋”芯片的工作,已經被移到了 CPU 的內部,所以你在主板上,已經看不到北橋芯片了。

馮·諾依曼體系結構

剛才我們講了一台計算機的硬件組成,這說的是我們平時用的個人電腦或者服務器。那我們平時最常用的智能手機的組成,也是這樣嗎?

我們手機里只有 SD 卡(Secure Digital Memory Card)這樣類似硬盤功能的存儲卡插槽,並沒有內存插槽、CPU 插槽這些東西。沒錯,因為手機尺寸的原因,手機制造商們選擇把 CPU、內存、網絡通信,乃至攝像頭芯片,都封裝到一個芯片,然后再嵌入到手機主板上。這種方式叫SoC,也就是 System on a Chip(系統芯片)。

這樣看起來,個人電腦和智能手機的硬件組成方式不太一樣。可是,我們寫智能手機上的 App,和寫個人電腦的客戶端應用似乎沒有什么差別,都是通過“高級語言”這樣的編程語言撰寫、編譯之后,一樣是把代碼和數據加載到內存里來執行。這是為什么呢?因為,無論是個人電腦、服務器、智能手機,還是 Raspberry Pi 這樣的微型卡片機,都遵循着同一個“計算機”的抽象概念。這是怎么樣一個“計算機”呢?這其實就是,計算機祖師爺之一馮·諾依曼(John von Neumann)提出的馮·諾依曼體系結構(Von Neumann architecture),也叫存儲程序計算機。

什么是存儲程序計算機呢?這里面其實暗含了兩個概念,一個是“可編程”計算機,一個是“存儲”計算機。

說到“可編程”,估計你會有點懵,你可以先想想,什么是“不可編程”。計算機是由各種門電路組合而成的,然后通過組裝出一個固定的電路版,來完成一個特定的計算程序。一旦需要修改功能,就要重新組裝電路。這樣的話,計算機就是“不可編程”的,因為程序在計算機硬件層面是“寫死”的。最常見的就是老式計算器,電路板設好了加減乘除,做不了任何計算邏輯固定之外的事情。

我們再來看“存儲”計算機。這其實是說,程序本身是存儲在計算機的內存里,可以通過加載不同的程序來解決不同的問題。有“存儲程序計算機”,自然也有不能存儲程序的計算機。典型的就是早年的“Plugboard”這樣的插線板式的計算機。整個計算機就是一個巨大的插線板,通過在板子上不同的插頭或者接口的位置插入線路,來實現不同的功能。這樣的計算機自然是“可編程”的,但是編寫好的程序不能存儲下來供下一次加載使用,不得不每次要用到和當前不同的“程序”的時候,重新插板子,重新“編程”。

可以看到,無論是“不可編程”還是“不可存儲”,都會讓使用計算機的效率大大下降。而這個對於效率的追求,也就是“存儲程序計算機”的由來。

於是我們的馮祖師爺,基於當時在秘密開發的 EDVAC 寫了一篇報告First Draft of a Report on the EDVAC,描述了他心目中的一台計算機應該長什么樣。這篇報告在歷史上有個很特殊的簡稱,叫First Draft,翻譯成中文,其實就是《第一份草案》。這樣,現代計算機的發展就從祖師爺寫的一份草案開始了。

First Draft里面說了一台計算機應該有哪些部分組成:

首先是一個包含算術邏輯單元(Arithmetic Logic Unit,ALU)和處理器寄存器(Processor Register)的處理器單元(Processing Unit),用來完成各種算術和邏輯運算。因為它能夠完成各種數據的處理或者計算工作,因此也有人把這個叫作數據通路(Datapath)或者運算器。

然后是一個包含指令寄存器(Instruction Reigster)和程序計數器(Program Counter)的控制器單元(Control Unit/CU),用來控制程序的流程,通常就是不同條件下的分支和跳轉。在現在的計算機里,上面的算術邏輯單元和這里的控制器單元,共同組成了我們說的 CPU。

接着是用來存儲數據(Data)和指令(Instruction)的內存。以及更大容量的外部存儲,在過去,可能是磁帶、磁鼓這樣的設備,現在通常就是硬盤。

最后就是各種輸入和輸出設備,以及對應的輸入和輸出機制。我們現在無論是使用什么樣的計算機,其實都是和輸入輸出設備在打交道。個人電腦的鼠標鍵盤是輸入設備,顯示器是輸出設備。我們用的智能手機,觸摸屏既是輸入設備,又是輸出設備。而跑在各種雲上的服務器,則是通過網絡來進行輸入和輸出。這個時候,網卡既是輸入設備又是輸出設備。

任何一台計算機的任何一個部件都可以歸到運算器、控制器、存儲器、輸入設備和輸出設備中,而所有的現代計算機也都是基於這個基礎架構來設計開發的。

而所有的計算機程序,也都可以抽象為從輸入設備讀取輸入信息,通過運算器和控制器來執行存儲在存儲器里的程序,最終把結果輸出到輸出設備中。而我們所有撰寫的無論高級還是低級語言的程序,也都是基於這樣一個抽象框架來進行運作的。

總結

可以說,馮·諾依曼體系結構確立了我們現在每天使用的計算機硬件的基礎架構。因此,學習計算機組成原理,其實就是學習和拆解馮·諾依曼體系結構。

具體來說,學習組成原理,其實就是學習控制器、運算器的工作原理,也就是 CPU 是怎么工作的,以及為何這樣設計;學習內存的工作原理,從最基本的電路,到上層抽象給到 CPU 乃至應用程序的接口是怎樣的;學習 CPU 是怎么和輸入設備、輸出設備打交道的。

學習組成原理,就是在理解從控制器、運算器、存儲器、輸入設備以及輸出設備,從電路這樣的硬件,到最終開放給軟件的接口,是怎么運作的,為什么要設計成這樣,以及在軟件開發層面怎么盡可能用好它。

從這張圖可以看出來,整個計算機組成原理,就是圍繞着計算機是如何組織運作展開的

計算機組成原理知識地圖

計算機組成原理的英文叫 Computer Organization。這里的 Organization 是“組織機構”的意思。計算機由很多個不同的部件放在一起,變成了一個“組織機構”。這個組織機構最終能夠進行各種計算、控制、讀取輸入,進行輸出,達成各種強大的功能。

在這張圖里面,我們把整個計算機組成原理的知識點拆分成了四大部分,分別是計算機的基本組成、計算機的指令和計算、處理器設計,以及存儲器和 I/O 設備。

首先,我們來看計算機的基本組成

這一部分,你需要學習計算機是由哪些硬件組成的。這些硬件,又是怎么對應到經典的馮·諾依曼體系結構中的,也就是運算器、控制器、存儲器、輸入設備和輸出設備這五大基本組件。除此之外,你還需要了解計算機的兩個核心指標,性能和功耗。性能和功耗也是我們在應用和設計五大基本組件中需要重點考慮的因素。

了解了組成部分,接下來你需要掌握計算機的指令和計算

在計算機指令部分,你需要搞明白,我們每天撰寫的一行行 C、Java、PHP 程序,是怎么在計算機里面跑起來的。這里面,你既需要了解我們的程序是怎么通過編譯器和匯編器,變成一條條機器指令這樣的編譯過程(如果把編譯過程展開的話,可以變成一門完整的編譯原理課程),還需要知道我們的操作系統是怎么鏈接、裝載、執行這些程序的(這部分知識如果再深入學習,又可以變成一門操作系統課程)。而這一條條指令執行的控制過程,就是由計算機五大組件之一的控制器來控制的。

在計算機的計算部分,你要從二進制和編碼開始,理解我們的數據在計算機里的表示,以及我們是怎么從數字電路層面,實現加法、乘法這些基本的運算功能的。實現這些運算功能的 ALU(Arithmetic Logic Unit/ALU),也就是算術邏輯單元,其實就是我們計算機五大組件之一的運算器

這里面有一個在今天看起來特別重要的知識點,就是浮點數(Floating Point)。浮點數是我們在日常運用中非常容易用錯的一種數據表示形式。掌握浮點數能讓你對數據的編碼、存儲和計算能夠有一個從表到里的深入理解。尤其在 AI 火熱的今天,浮點數是機器學習中重度使用的數據表示形式,掌握它更是非常有必要。

明白計算機指令和計算是如何運轉的,我們就可以深入到CPU 的設計中去一探究竟了。

CPU 時鍾可以用來構造寄存器和內存的鎖存器和觸發器,因此,CPU 時鍾應該是我們學習 CPU 的前導知識。搞明白我們為什么需要 CPU 時鍾(CPU Clock),以及寄存器和內存是用什么樣的硬件組成的之后,我們可以再來看看,整個計算機的數據通路是如何構造出來的。

數據通路,其實就是連接了整個運算器和控制器,並最終組成了 CPU。而出於對於性能和功耗的考慮,你要進一步理解和掌握面向流水線設計的 CPU、數據和控制冒險,以及分支預測的相關技術。

既然 CPU 作為控制器要和輸入輸出設備通信,那么我們就要知道異常和中斷發生的機制。在 CPU 設計部分的最后,我會講一講指令的並行執行,看看如何直接在 CPU 層面,通過 SIMD 來支持並行計算。

最后,我們需要看一看,計算機五大組成部分之一,存儲器的原理。通過存儲器的層次結構作為基礎的框架引導,你需要掌握從上到下的 CPU 高速緩存、內存、SSD 硬盤和機械硬盤的工作原理,它們之間的性能差異,以及實際應用中利用這些設備會遇到的挑戰。存儲器其實很多時候又扮演了輸入輸出設備的角色,所以你需要進一步了解,CPU 和這些存儲器之間是如何進行通信的,以及我們最重視的性能問題是怎么一回事;理解什么是 IO_WAIT,如何通過 DMA 來提升程序性能。

對於存儲器,我們不僅需要它們能夠正常工作,還要確保里面的數據不能丟失。於是你要掌握我們是如何通過 RAID、Erasure Code、ECC 以及分布式 HDFS,這些不同的技術,來確保數據的完整性和訪問性能。

“性能”究竟是什么

“性能”這個詞,不管是在日常生活還是寫程序的時候,都經常被提到。比方說,

買新電腦的時候,我們會說“原來的電腦性能跟不上了”;

寫程序的時候,我們會說,“這個程序性能需要優化一下”。

那么,你有沒有想過,我們常常掛在嘴邊的“性能”到底指的是什么呢?我們能不能給性能下一個明確的定義,然后來進行准確的比較呢?

什么是性能?時間的倒數

在計算機組成原理乃至體系結構中,“性能”都是最重要的一個主題。學習和研究計算機組成原理,就是在理解計算機是怎么運作的,以及為什么要這么運作。“為什么”所要解決的事情,很多時候就是提升“性能”。

計算機的性能,其實和我們干體力勞動很像,好比是我們要搬東西。對於計算機的性能,我們需要有個標准來衡量。這個標准中主要有兩個指標。

第一個是響應時間(Response time)或者叫執行時間(Execution time)。想要提升響應時間這個性能指標,你可以理解為讓計算機“跑得更快”。

第二個是吞吐率(Throughput)或者帶寬(Bandwidth),想要提升這個指標,你可以理解為讓計算機“搬得更多”。

所以說,響應時間指的就是,我們執行一個程序,到底需要花多少時間。花的時間越少,自然性能就越好。

而吞吐率是指我們在一定的時間范圍內,到底能處理多少事情。這里的“事情”,在計算機里就是處理的數據或者執行的程序指令。

和搬東西來做對比,如果我們的響應時間短,跑得快,我們可以來回多跑幾趟多搬幾趟。所以說,縮短程序的響應時間,一般來說都會提升吞吐率。

除了縮短響應時間,我們還有別的方法嗎?當然有,比如說,我們還可以多找幾個人一起來搬,這就類似現代的服務器都是 8 核、16 核的。人多力量大,同時處理數據,在單位時間內就可以處理更多數據,吞吐率自然也就上去了。

提升吞吐率的辦法有很多。大部分時候,我們只要多加一些機器,多堆一些硬件就好了。但是響應時間的提升卻沒有那么容易,因為 CPU 的性能提升其實在 10 年前就處於“擠牙膏”的狀態了,所以我們得慎重地來分析對待。下面我們具體來看。

我們一般把性能,定義成響應時間的倒數,也就是:

\[性能 = 1/響應時間 \]

這樣一來,響應時間越短,性能的數值就越大。同樣一個程序,在 Intel 最新的 CPU Coffee Lake 上,只需要 30s 就能運行完成,而在 5 年前 CPU Sandy Bridge 上,需要 1min 才能完成。那么我們自然可以算出來,Coffee Lake 的性能是 1/30,Sandy Bridge 的性能是 1/60,兩個的性能比為 2。於是,我們就可以說,Coffee Lake 的性能是 Sandy Bridge 的 2 倍。

過去幾年流行的手機跑分軟件,就是把多個預設好的程序在手機上運行,然后根據運行需要的時間,算出一個分數來給出手機的性能評估。而在業界,各大 CPU 和服務器廠商組織了一個叫作SPEC(Standard Performance Evaluation Corporation)的第三方機構,專門用來指定各種“跑分”的規則。

SPEC 提供的 CPU 基准測試程序,就好像 CPU 屆的“高考”,通過數十個不同的計算程序,對於 CPU 的性能給出一個最終評分。這些程序豐富多彩,有編譯器、解釋器、視頻壓縮、人工智能國際象棋等等,涵蓋了方方面面的應用場景。

計算機的計時單位:CPU 時鍾

雖然時間是一個很自然的用來衡量性能的指標,但是用時間來衡量時,有兩個問題。

第一個就是時間不“准”。如果用你自己隨便寫的一個程序,來統計程序運行的時間,每一次統計結果不會完全一樣。有可能這一次花了 45ms,下一次變成了 53ms。

為什么會不准呢?這里面有好幾個原因。

首先,我們統計時間是用類似於“掐秒表”一樣,記錄程序運行結束的時間減去程序開始運行的時間。這個時間也叫 Wall Clock Time 或者 Elapsed Time,就是在運行程序期間,掛在牆上的鍾走掉的時間。

但是,計算機可能同時運行着好多個程序,CPU 實際上不停地在各個程序之間進行切換。在這些走掉的時間里面,很可能 CPU 切換去運行別的程序了。而且,有些程序在運行的時候,可能要從網絡、硬盤去讀取數據,要等網絡和硬盤把數據讀出來,給到內存和 CPU。

所以說,要想准確統計某個程序運行時間,進而去比較兩個程序的實際性能,我們得把這些時間給刨除掉。

那這件事怎么實現呢?

Linux 下有一個叫 time 的命令,可以幫我們統計出來,同樣的 Wall Clock Time 下,程序實際在 CPU 上到底花了多少時間。

我們簡單運行一下 time 命令。

它會返回三個值,

第一個是real time,也就是我們說的 Wall Clock Time,也就是運行程序整個過程中流逝掉的時間;

第二個是user time,也就是 CPU 在運行你的程序,在用戶態運行指令的時間;

第三個是sys time,是 CPU 在運行你的程序,在操作系統內核里運行指令的時間。

而程序實際花費的 CPU 執行時間(CPU Time),就是 user time 加上 sys time。

$ time seq 1000000 | wc -l
1000000
 
 
real  0m0.101s
user  0m0.031s
sys   0m0.016s

在這個例子里,你可以看到,實際上程序用了 0.101s,但是 CPU time 只有 0.031+0.016 = 0.047s。運行程序的時間里,只有不到一半是實際花在這個程序上的。

其次,即使我們已經拿到了 CPU 時間,我們也不一定可以直接“比較”出兩個程序的性能差異。即使在同一台計算機上,CPU 可能滿載運行也可能降頻運行,降頻運行的時候自然花的時間會多一些。

除了 CPU 之外,時間這個性能指標還會受到主板、內存這些其他相關硬件的影響。所以,我們需要對“時間”這個我們可以感知的指標進行拆解,把程序的 CPU 執行時間變成 CPU 時鍾周期數(CPU Cycles)和 時鍾周期時間(Clock Cycle)的乘積。

\[程序的 CPU 執行時間 =CPU 時鍾周期數×時鍾周期時間 \]

我們先來理解一下什么是時鍾周期時間。

你在買電腦的時候,一定關注過 CPU 的主頻。比如Intel Core-i7-7700HQ 2.8GHz,這里的 2.8GHz 就是電腦的主頻(Frequency/Clock Rate)。這個 2.8GHz,我們可以先粗淺地認為,CPU 在 1 秒時間內,可以執行的簡單指令的數量是 2.8G 條。

如果想要更准確一點描述,這個 2.8GHz 就代表,我們 CPU 的一個“鍾表”能夠識別出來的最小的時間間隔。就像我們掛在牆上的掛鍾,都是“滴答滴答”一秒一秒地走,所以通過牆上的掛鍾能夠識別出來的最小時間單位就是秒。

而在 CPU 內部,和我們平時戴的電子石英表類似,有一個叫晶體振盪器(Oscillator Crystal)的東西,簡稱為晶振。我們把晶振當成 CPU 內部的電子表來使用。晶振帶來的每一次“滴答”,就是時鍾周期時間。

在我這個 2.8GHz 的 CPU 上,這個時鍾周期時間,就是 1/2.8G。我們的 CPU,是按照這個“時鍾”提示的時間來進行自己的操作。主頻越高,意味着這個表走得越快,我們的 CPU 也就“被逼”着走得越快。

如果你自己組裝過台式機的話,可能聽說過“超頻”這個概念,這說的其實就相當於把買回來的 CPU 內部的鍾給調快了,於是 CPU 的計算跟着這個時鍾的節奏,也就自然變快了。當然這個快不是沒有代價的,CPU 跑得越快,散熱的壓力也就越大。就和人一樣,超過生理極限,CPU 就會崩潰了。

我們現在回到上面程序 CPU 執行時間的公式。

\[程序的 CPU 執行時間 =CPU 時鍾周期數×時鍾周期時間 \]

最簡單的提升性能方案,自然縮短時鍾周期時間,也就是提升主頻。換句話說,就是換一塊好一點的 CPU。不過,這個是我們這些軟件工程師控制不了的事情,所以我們就把目光挪到了乘法的另一個因子——CPU 時鍾周期數上。如果能夠減少程序需要的 CPU 時鍾周期數量,一樣能夠提升程序性能。

對於 CPU 時鍾周期數,我們可以再做一個分解,把它變成“指令數×每條指令的平均時鍾周期數(Cycles Per Instruction,簡稱 CPI)”。不同的指令需要的 Cycles 是不同的,加法和乘法都對應着一條 CPU 指令,但是乘法需要的 Cycles 就比加法要多,自然也就慢。在這樣拆分了之后,我們的程序的 CPU 執行時間就可以變成這樣三個部分的乘積。

\[程序的 CPU 執行時間 = 指令數×CPI×Clock Cycle Time \]

因此,如果我們想要解決性能問題,其實就是要優化這三者。

  1. 時鍾周期時間,就是計算機主頻,這個取決於計算機硬件。我們所熟知的摩爾定律就一直在不停地提高我們計算機的主頻。比如說,我最早使用的 80386 主頻只有 33MHz,現在手頭的筆記本電腦就有 2.8GHz,在主頻層面,就提升了將近 100 倍。

  2. 每條指令的平均時鍾周期數 CPI,就是一條指令到底需要多少 CPU Cycle。之后講解 CPU 結構的時候,我們會看到,現代的 CPU 通過流水線技術(Pipeline),讓一條指令需要的 CPU Cycle 盡可能地少。因此,對於 CPI 的優化,也是計算機組成和體系結構中的重要一環。

  3. 指令數,代表執行我們的程序到底需要多少條指令、用哪些指令。這個很多時候就把挑戰交給了編譯器。同樣的代碼,編譯成計算機指令時候,就有各種不同的表示方式。

我們可以把自己想象成一個 CPU,坐在那里寫程序。

計算機主頻就好像是你的打字速度,打字越快,你自然可以多寫一點程序。

CPI 相當於你在寫程序的時候,熟悉各種快捷鍵,越是打同樣的內容,需要敲擊鍵盤的次數就越少。

指令數相當於你的程序設計得夠合理,同樣的程序要寫的代碼行數就少。

如果三者皆能實現,你自然可以很快地寫出一個優秀的程序,你的“性能”從外面來看就是好的。

總結

主要對於“響應時間”這個性能指標進行抽絲剝繭,拆解成了計算機時鍾周期、CPI 以及指令數這三個獨立的指標的乘積,並且為你指明了優化計算機性能的三條康庄大道。也就是,提升計算機主頻,優化 CPU 設計使得在單個時鍾周期內能夠執行更多指令,以及通過編譯器來減少需要的指令數。

該從哪些方面提升“性能”?

從 1978 年 Intel 發布的 8086 CPU 開始,計算機的主頻從 5MHz 開始,不斷提升。1980 年代中期的 80386 能夠跑到 40MHz,1989 年的 486 能夠跑到 100MHz,直到 2000 年的奔騰 4 處理器,主頻已經到達了 1.4GHz。而消費者也在這 20 年里養成了“看主頻”買電腦的習慣。當時已經基本壟斷了桌面 CPU 市場的 Intel 更是誇下了海口,表示奔騰 4 所使用的 CPU 結構可以做到 10GHz,頗有一點“大力出奇跡”的意思。

功耗:CPU 的“人體極限”

然而,計算機科學界從來不相信“大力出奇跡”。奔騰 4 的 CPU 主頻從來沒有達到過 10GHz,最終它的主頻上限定格在 3.8GHz。這還不是最糟的,更糟糕的事情是,大家發現,奔騰 4 的主頻雖然高,但是它的實際性能卻配不上同樣的主頻。想要用在筆記本上的奔騰 4 2.4GHz 處理器,其性能只和基於奔騰 3 架構的奔騰 M 1.6GHz 處理器差不多。

於是,這一次的“大力出悲劇”,不僅讓 Intel 的對手 AMD 獲得了喘息之機,更是代表着“主頻時代”的終結。后面幾代 Intel CPU 主頻不但沒有上升,反而下降了。到如今,2019 年的最高配置 Intel i9 CPU,主頻也只不過是 5GHz 而已。相較於 1978 年到 2000 年,這 20 年里 300 倍的主頻提升,從 2000 年到現在的這 19 年,CPU 的主頻大概提高了 3 倍。

奔騰 4 的主頻為什么沒能超過 3.8GHz 的障礙呢?答案就是功耗問題。什么是功耗問題呢?我們先看一個直觀的例子。

一個 3.8GHz 的奔騰 4 處理器,滿載功率是 130 瓦。這個 130 瓦是什么概念呢?機場允許帶上飛機的充電寶的容量上限是 100 瓦時。如果我們把這個 CPU 安在手機里面,不考慮屏幕內存之類的耗電,這個 CPU 滿載運行 45 分鍾,充電寶里面就沒電了。而 iPhone X 使用 ARM 架構的 CPU,功率則只有 4.5 瓦左右。

我們的 CPU,一般都被叫作超大規模集成電路(Very-Large-Scale Integration,VLSI)。這些電路,實際上都是一個個晶體管組合而成的。CPU 在計算,其實就是讓晶體管里面的“開關”不斷地去“打開”和“關閉”,來組合完成各種運算和功能。

想要計算得快,

一方面,我們要在 CPU 里,同樣的面積里面,多放一些晶體管,也就是增加密度

另一方面,我們要讓晶體管“打開”和“關閉”得更快一點,也就是提升主頻

而這兩者,都會增加功耗,帶來耗電和散熱的問題。

這么說可能還是有點抽象,我還是給你舉一個例子。你可以把一個計算機 CPU 想象成一個巨大的工廠,里面有很多工人,相當於 CPU 上面的晶體管,互相之間協同工作。

為了工作得快一點,我們要在工廠里多塞一點人。你可能會問,為什么不把工廠造得大一點呢?這是因為,人和人之間如果離得遠了,互相之間走過去需要花的時間就會變長,這也會導致性能下降。這就好像如果 CPU 的面積大,晶體管之間的距離變大,電信號傳輸的時間就會變長,運算速度自然就慢了。

除了多塞一點人,我們還希望每個人的動作都快一點,這樣同樣的時間里就可以多干一點活兒了。這就相當於提升 CPU 主頻,但是動作快,每個人就要出汗散熱。要是太熱了,對工廠里面的人來說會中暑生病,對 CPU 來說就會崩潰出錯。

我們會在 CPU 上面抹硅脂、裝風扇,乃至用上水冷或者其他更好的散熱設備,就好像在工廠里面裝風扇、空調,發冷飲一樣。但是同樣的空間下,裝上風扇空調能夠帶來的散熱效果也是有極限的。

因此,在 CPU 里面,能夠放下的晶體管數量和晶體管的“開關”頻率也都是有限的。一個 CPU 的功率,可以用這樣一個公式來表示:

\[功耗 ~= 1/2 ×負載電容×電壓的平方×開關頻率×晶體管數量 \]

那么,為了要提升性能,我們需要不斷地增加晶體管數量。同樣的面積下,我們想要多放一點晶體管,就要把晶體管造得小一點。這個就是平時我們所說的提升“制程”。從 28nm 到 7nm,相當於晶體管本身變成了原來的 1/4 大小。這個就相當於我們在工廠里,同樣的活兒,我們要找瘦小一點的工人,這樣一個工廠里面就可以多一些人。我們還要提升主頻,讓開關的頻率變快,也就是要找手腳更快的工人。

但是,功耗增加太多,就會導致 CPU 散熱跟不上,這時,我們就需要降低電壓。這里有一點非常關鍵,在整個功耗的公式里面,功耗和電壓的平方是成正比的。這意味着電壓下降到原來的 1/5,整個的功耗會變成原來的 1/25。

事實上,從 5MHz 主頻的 8086 到 5GHz 主頻的 Intel i9,CPU 的電壓已經從 5V 左右下降到了 1V 左右。這也是為什么我們 CPU 的主頻提升了 1000 倍,但是功耗只增長了 40 倍。

並行優化,理解阿姆達爾定律

雖然制程的優化和電壓的下降,在過去的 20 年里,讓我們的 CPU 性能有所提升。但是從上世紀九十年代到本世紀初,軟件工程師們所用的“面向摩爾定律編程”的套路越來越用不下去了。“寫程序不考慮性能,等明年 CPU 性能提升一倍,到時候性能自然就不成問題了”,這種想法已經不可行了。

於是,從奔騰 4 開始,Intel 意識到通過提升主頻比較“難”去實現性能提升,邊開始推出 Core Duo 這樣的多核 CPU,通過提升“吞吐率”而不是“響應時間”,來達到目的。

提升響應時間,就好比提升你用的交通工具的速度,比如原本你是開汽車,現在變成了火車乃至飛機。本來開車從上海到北京要 20 個小時,換成飛機就只要 2 個小時了,但是,在此之上,再想要提升速度就不太容易了。我們的 CPU 在奔騰 4 的年代,就好比已經到了飛機這個速度極限。

那你可能要問了,接下來該怎么辦呢?相比於給飛機提速,工程師們又想到了新的辦法,可以一次同時開 2 架、4 架乃至 8 架飛機,這就好像我們現在用的 2 核、4 核,乃至 8 核的 CPU。

雖然從上海到北京的時間沒有變,但是一次飛 8 架飛機能夠運的東西自然就變多了,也就是所謂的“吞吐率”變大了。所以,不管你有沒有需要,現在 CPU 的性能就是提升了 2 倍乃至 8 倍、16 倍。這也是一個最常見的提升性能的方式,通過並行提高性能。

這個思想在很多地方都可以使用。舉個例子,我們做機器學習程序的時候,需要計算向量的點積,比如向量

\[W=[W0,W1,W2,…,W15]W=[W0,W1,W2,…,W15] \]

和向量

\[X=[X0,X1,X2,…,X15]X=[X0,X1,X2,…,X15] \]

\[W⋅X=W0∗X0+W1∗X1+W·X=W0∗X0+W1∗X1+W2∗X2+…+W15∗X15W2∗X2+…+W15∗X15 \]

。這些式子由 16 個乘法和 1 個連加組成。如果你自己一個人用筆來算的話,需要一步一步算 16 次乘法和 15 次加法。如果這個時候我們把這個人物分配給 4 個人,同時去算

\[W0~W3W0~W3, W4~W7W4~W7, W8~W11W8~W11, W12~W15W12~W15 \]

這樣四個部分的結果,再由一個人進行匯總,需要的時間就會縮短。

但是,並不是所有問題,都可以通過並行提高性能來解決。如果想要使用這種思想,需要滿足這樣幾個條件。

第一,需要進行的計算,本身可以分解成幾個可以並行的任務。好比上面的乘法和加法計算,幾個人可以同時進行,不會影響最后的結果。

第二,需要能夠分解好問題,並確保幾個人的結果能夠匯總到一起。

第三,在“匯總”這個階段,是沒有辦法並行進行的,還是得順序執行,一步一步來。

這就引出了我們在進行性能優化中,常常用到的一個經驗定律,阿姆達爾定律(Amdahl’s Law)。這個定律說的就是,對於一個程序進行優化之后,處理器並行運算之后效率提升的情況。具體可以用這樣一個公式來表示:

\[優化后的執行時間 = 受優化影響的執行時間 / 加速倍數 + 不受影響的執行時間 \]

在剛剛的向量點積例子里,4 個人同時計算向量的一小段點積,就是通過並行提高了這部分的計算性能。但是,這 4 個人的計算結果,最終還是要在一個人那里進行匯總相加。這部分匯總相加的時間,是不能通過並行來優化的,也就是上面的公式里面不受影響的執行時間這一部分。

比如上面的各個向量的一小段的點積,需要 100ns,加法需要 20ns,總共需要 120ns。這里通過並行 4 個 CPU 有了 4 倍的加速度。那么最終優化后,就有了 100/4+20=45ns。即使我們增加更多的並行度來提供加速倍數,比如有 100 個 CPU,整個時間也需要 100/100+20=21ns。

總結

我們可以看到,無論是簡單地通過提升主頻,還是增加更多的 CPU 核心數量,通過並行來提升性能,都會遇到相應的瓶頸。僅僅簡單地通過“堆硬件”的方式,在今天已經不能很好地滿足我們對於程序性能的期望了。於是,工程師們需要從其他方面開始下功夫了。

在“摩爾定律”和“並行計算”之外,在整個計算機組成層面,還有這樣幾個原則性的性能提升方法。

  1. 加速大概率事件。最典型的就是,過去幾年流行的深度學習,整個計算過程中,99% 都是向量和矩陣計算,於是,工程師們通過用 GPU 替代 CPU,大幅度提升了深度學習的模型訓練過程。本來一個 CPU 需要跑幾小時甚至幾天的程序,GPU 只需要幾分鍾就好了。Google 更是不滿足於 GPU 的性能,進一步地推出了 TPU。后面的文章,我也會為你講解 GPU 和 TPU 的基本構造和原理。

  2. 通過流水線提高性能。現代的工廠里的生產線叫“流水線”。我們可以把裝配 iPhone 這樣的任務拆分成一個個細分的任務,讓每個人都只需要處理一道工序,最大化整個工廠的生產效率。類似的,我們的 CPU 其實就是一個“運算工廠”。我們把 CPU 指令執行的過程進行拆分,細化運行,也是現代 CPU 在主頻沒有辦法提升那么多的情況下,性能仍然可以得到提升的重要原因之一。我們在后面也會講到,現代 CPU 里是如何通過流水線來提升性能的,以及反面的,過長的流水線會帶來什么新的功耗和效率上的負面影響。

  3. 通過預測提高性能。通過預先猜測下一步該干什么,而不是等上一步運行的結果,提前進行運算,也是讓程序跑得更快一點的辦法。典型的例子就是在一個循環訪問數組的時候,憑經驗,你也會猜到下一步我們會訪問數組的下一項。后面要講的“分支和冒險”、“局部性原理”這些 CPU 和存儲系統設計方法,其實都是在利用我們對於未來的“預測”,提前進行相應的操作,來提升我們的程序性能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM