- Python高性能編程(第2版)
- (美)米夏·戈雷利克等
- 4553字
- 2023-09-06 19:21:25
1.1 基本的計算機系統(tǒng)
計算機的底層部件可簡化為3個基本部分:計算單元、存儲單元以及它們之間的連接。這些單元有各種不同的屬性,可幫助我們認識它們。計算單元的一個屬性是每秒能執(zhí)行多少次計算;存儲單元的屬性包括可存儲多少數(shù)據(jù)以及讀寫數(shù)據(jù)的速度;連接的一個屬性是,通過它能夠以多快的速度將數(shù)據(jù)從一個地方移到另一個地方。
通過這些基本單元,我們可以在多個復(fù)雜級別上討論標準工作站。例如,可認為標準工作站有一個充當計算單元的中央處理器(Central Processing Unit,CPU),它連接到兩個獨立的存儲單元——隨機存取存儲器(Random Access Memory,RAM)和硬盤(它們的容量和讀寫速度不同),而所有這些單元都是通過一條總線連接起來的。然而,也可以更詳細地了解CPU本身的幾個存儲單元——L1、L2乃至L3和L4緩存,它們的容量很小(從幾KB到十多MB不等),但速度非常快。另外,新型計算機架構(gòu)通常采用了新配置,例如,Intel SkyLake CPU使用的是快速信道互聯(lián)(Ultra Path Interconnect),而不是前端總線,還重構(gòu)了眾多其他的連接。最后,在這兩種有關(guān)工作站的近似描述中,都省略了網(wǎng)絡(luò)連接;網(wǎng)絡(luò)連接的速度很慢,可用于連接到眾多其他的計算單元和存儲單元。
為幫助你理清頭緒,下面簡要地描述一下這些基本單元。
1.1.1 計算單元
計算單元是計算機的核心,它能夠?qū)?shù)據(jù)進行轉(zhuǎn)換,還能改變當前進程的狀態(tài)。最常見的計算單元是CPU,但圖形處理單元(Graphic Processing Unit,GPU)正越來越多地用作輔助計算單元。GPU最初用于提高計算機圖形的處理速度,但憑借其內(nèi)在的并行特性,它們越來越適合用于數(shù)值應(yīng)用程序;所謂并行,指的是支持同時執(zhí)行眾多計算。無論是哪種類型的計算單元,它們都接收一系列位(如表示數(shù)字的位),并輸出另一組位(如表示數(shù)字之和的位)。計算單元可對整數(shù)和實數(shù)執(zhí)行算術(shù)運算、對二進制數(shù)執(zhí)行按位運算,有些計算單元還可執(zhí)行特殊的運算,如加法和乘法混合運算:接收3個數(shù)字A、B和C,并返回A*B+C的值。
計算單元的主要屬性有兩個:一個周期內(nèi)可執(zhí)行多少次計算;每秒可完成多少個周期。其中第一個屬性的度量指標為指令數(shù)/周期(Instruction Per Cycle,IPC)[1],而第二個屬性的度量指標為時鐘速度。設(shè)計新的計算單元時,這兩個指標會相互制約。例如,Intel Core系列的IPC很高,但時鐘速度較低,而Pentium 4芯片則相反。GPU的IPC和時鐘速度都很高,但它們存在其他問題,如通信速度慢,這將在1.1.3節(jié)討論。
[1] 第9章將討論的進程間通信(InterProcess Communication)的縮略語也是IPC,請不要將它們混為一談。
另外,盡管通過提高時鐘速度幾乎可以立即提高計算單元上運行的所有程序的速度(因為這讓程序能夠在單位時間內(nèi)執(zhí)行更多的計算),但提高IPC也可改善向量化水平,從而極大地影響計算。向量化(vectorization)發(fā)生在一次給CPU提供多項數(shù)據(jù),并且它能夠同時對這些數(shù)據(jù)執(zhí)行運算的時候。這種CPU指令被稱為單指令多數(shù)據(jù)(Single Instruction Multiple Data,SIMD)。
在過去的10年,計算單元的發(fā)展速度極其緩慢,如圖1-1所示。時鐘速度和IPC都停滯不前,這是因為晶體管的尺寸已接近物理極限。有鑒于此,芯片制造商已轉(zhuǎn)而采用其他方法來提高速度,包括多線程技術(shù)(同時執(zhí)行多個線程)、更巧妙的亂序執(zhí)行以及多核架構(gòu)。

圖1-1 CPU時鐘頻率變化趨勢(摘自CPU DB)
超線程技術(shù)讓主機操作系統(tǒng)(Operating System,OS)以為有另一個CPU;更巧妙的硬件邏輯試圖在單個CPU中交替地執(zhí)行兩個指令線程。如果成功,在單線程上最多可以將速度提高30%。通常,如果兩個線程的工作分布在不同的執(zhí)行單元上(如一個執(zhí)行浮點數(shù)運算,另一個執(zhí)行整數(shù)運算),這樣做的效果非常好。
亂序執(zhí)行讓編譯器找出程序中彼此獨立的部分,進而以任何順序或同時執(zhí)行它們。只要能在正確的時間提供中間結(jié)果,程序就能往下正確地執(zhí)行,哪怕沒有按程序指定的順序提供這些結(jié)果。這使得在有些指令被阻塞(如等待內(nèi)存訪問)時,可執(zhí)行其他指令,從而提高可用資源的利用率。
最后是多核架構(gòu)的普及,這對高級程序員來說也是最重要的一點。這種架構(gòu)包含多個CPU,無須突破單CPU的速度壁壘就能提高總體計算能力。這就是當前很難找到少于雙核的計算機的原因所在(所謂雙核計算機,就是有兩個彼此相連的計算單元)。這雖然增加了每秒可執(zhí)行的運算數(shù),但也增加了代碼編寫工作的難度。
根據(jù)阿姆達爾定律,給CPU增加核心并不一定能縮短程序的執(zhí)行時間。簡單地說,阿姆達爾定律是這樣的:對于在多核上運行的程序,如果其中有些子程序必須在同一個核上運行,將對速度提升帶來限制,使得速度提升到一定程度后,即便再增加核心,也無法進一步提高速度。
例如,假設(shè)有項調(diào)查,需要調(diào)查100人,而調(diào)查每個人需要1分鐘才能完成。假設(shè)只有一位調(diào)查員,則完成這項調(diào)查任務(wù)將需要100分鐘(這位調(diào)查員向第1位被調(diào)查者提問并等待回答,再轉(zhuǎn)向第2位被調(diào)查者)。這種由一位調(diào)查員提問并等待回答的方法類似于串行處理;在串行處理中,每次只能執(zhí)行一項操作,每項操作都需要等待前一項操作執(zhí)行完畢后再執(zhí)行。
如果有兩位調(diào)查員,就可以同時進行調(diào)查,整個調(diào)查工作只需50分鐘就能完成。這是因為每位調(diào)查員都無須了解另一位調(diào)查員的調(diào)查情況,因此可將任務(wù)分成兩個彼此獨立的部分。
增加調(diào)查員可進一步提高速度。當調(diào)查員數(shù)量增加到100時,整個調(diào)查過程只需1分鐘就能完成,這是被調(diào)查者回答問題所需的時間。如果此時再增加調(diào)查員,并不能進一步縮短調(diào)查時間,因為新增的調(diào)查員將無所事事——所有的被調(diào)查者都在接受調(diào)查。在這種情況下,要縮短整個調(diào)查時間,唯一的辦法是縮短調(diào)查單人所需的時間,即完成問題的串行部分所需的時間。同樣,對于CPU,可在必要時增加執(zhí)行計算的核心,但增加到一定程度后將出現(xiàn)瓶頸,這個瓶頸就是特定核心完成其任務(wù)所需的時間。換言之,在并行計算中,瓶頸是由必須串行執(zhí)行的子任務(wù)決定的。
另外,在Python中利用多核將面臨的一個主要障礙是,Python使用了全局解釋器鎖(Global Interpreter Lock,GIL)。GIL確保Python進程每次只運行一條指令,而不管當前使用了多少個核心。這意味著雖然有些Python代碼能夠同時訪問多個核心,但在任何時點,都只有一個核心在運行Python指令。如果以前面的調(diào)查為例,這意味著即便有100名調(diào)查員,任何給定時點也只有一位調(diào)查員在提問并等待回答,這相當于讓多位調(diào)查員帶來的好處消失殆盡。這看起來是個巨大的障礙,如果考慮到當前的趨勢是使用多個計算單元而不是單個速度更快的計算單元,這個問題就更嚴重了。采用如下方法可避免這個問題:使用諸如multiprocessing等標準庫工具(第9章);使用諸如numpy、numexpr等技術(shù)(第6章);使用Cython(第7章);使用分布式計算模型(第10章)。
注意:Python 3.2對GIL做了重大修改,使其更靈活,從而緩解了眾多與單線程性能相關(guān)的問題。雖然GIL現(xiàn)在依然對Python進行限制,使其每次只能運行一條指令,但在指令切換方面它做得更好,且開銷更低。
1.1.2 存儲單元
計算機的存儲單元用于存儲位,這些位表示的可能是程序中的變量,也可能是圖像像素。存儲單元包括主板上的寄存器、RAM和硬盤,這些存儲單元的主要不同之處在于讀寫數(shù)據(jù)的速度。讀寫速度嚴重依賴于讀寫方式。
例如,對大多數(shù)存儲單元來說,讀取一大塊數(shù)據(jù)時,性能要比讀取大量小塊數(shù)據(jù)高得多,這兩種讀取方式分別稱為順序讀取和隨機讀取。如果將數(shù)據(jù)視為書本中的書頁,這意味著對大多數(shù)存儲單元來說,連續(xù)翻頁的速度要比隨機翻頁快。所有存儲單元的順序讀取速度都快于隨機讀取,但在不同的存儲單元中,這兩種讀取方式的速度有天壤之別。
除了讀寫速度,存儲單元的另一個屬性是延遲,這可用查找數(shù)據(jù)所需的時間來表征。旋轉(zhuǎn)式硬盤的延遲可能很長,因為需要讓磁盤旋轉(zhuǎn)起來,并將讀寫頭移到合適的位置;而RAM的延遲可能很短,因為一切都是固態(tài)的。下面按讀寫速度從低到高的順序簡要地介紹一下標準工作站中常見的存儲單元[2]。
[2] 本節(jié)的速度數(shù)據(jù)摘自O(shè)’Reilly網(wǎng)站。
1)旋轉(zhuǎn)硬盤
旋轉(zhuǎn)硬盤屬于永久性存儲,即便計算機關(guān)機也不會丟失。它的讀寫速度通常較慢,因為必須旋轉(zhuǎn)磁盤并移動讀寫頭。隨機存取時性能將下降,但容量非常大(數(shù)十TB)。
2)固態(tài)硬盤
固態(tài)硬盤類似于旋轉(zhuǎn)硬盤,但讀寫速度更快,容量更小(數(shù)TB)。
3)RAM
RAM用于存儲應(yīng)用程序的代碼和數(shù)據(jù)(如應(yīng)用程序中所有的變量)。讀寫速度較快,隨機存取的性能也不錯,但通常容量有限(64 GB左右)。
4)L1/L2緩存
讀寫速度非常快。進入CPU的數(shù)據(jù)必須經(jīng)過這些緩存。容量很小(數(shù)MB)。
圖1-2說明了這些存儲單元的特征。
一個顯而易見的規(guī)律是,讀寫速度和容量成反比:速度越快,容量越小。有鑒于此,很多系統(tǒng)都實現(xiàn)了分層存儲:所有數(shù)據(jù)都存儲在硬盤中,部分數(shù)據(jù)進入RAM,更少的部分數(shù)據(jù)進入L1/L2緩存。這種分層方法讓程序能夠根據(jù)存取速度需求將數(shù)據(jù)放在不同的地方。優(yōu)化程序的數(shù)據(jù)存儲模式時,優(yōu)化的是數(shù)據(jù)的存儲位置和布局(旨在增加順序讀取的次數(shù))以及在不同存儲單元之間移動數(shù)據(jù)的次數(shù)。另外,異步I/O和搶占式緩存(preemptive caching)可確保數(shù)據(jù)位于合適的地方,從而避免浪費計算時間,這是因為這些過程是獨立于計算的。

圖1-2 各種存儲單元的特征值(2014年2月的數(shù)據(jù))
1.1.3 通信層
最后,我們來看看這些基本部件是如何相互通信的。通信模式有很多,但都是總線的變種。
例如,前端總線是RAM和L1/L2緩存之間的連接,它將準備就緒的數(shù)據(jù)移到集結(jié)場所,供處理器進行轉(zhuǎn)換,并將計算結(jié)果移出。還有其他的總線,如外部總線,它是硬件設(shè)備(如硬盤和網(wǎng)卡)到CPU和系統(tǒng)內(nèi)存的主要通道。外部總線的速度通常比前端總線慢。
實際上,L1/L2緩存帶來的很多好處都要歸功于速度更快的總線。正是因為能夠在慢速總線(RAM到緩存的總線)以大塊的方式將計算所需的數(shù)據(jù)排隊,再通過從緩存到CPU的總線以非常快的速度提供數(shù)據(jù),才讓CPU無須等待太長時間,從而能夠執(zhí)行更多的計算。
同樣,使用GPU存在的眾多缺點也是由其連接的總線導(dǎo)致的:GPU通常是外部設(shè)備,通過PCI總線進行通信,而這種總線的速度比前端總線慢得多,因此將數(shù)據(jù)移入和移出GPU可能是一項開銷高昂的操作。有鑒于此,異構(gòu)計算(前端總線連接CPU和GPU)應(yīng)運而生,它旨在降低數(shù)據(jù)傳輸開銷,讓GPU計算變得可行,即便在需要傳輸大量數(shù)據(jù)的情況下亦如此。
除了計算機內(nèi)部的通信部件,另一種通信部件是網(wǎng)絡(luò)設(shè)備,它比前面討論的通信部件更靈活,可連接到存儲設(shè)備,如網(wǎng)絡(luò)連接存儲(Network Attached Storage,NAS)設(shè)備,還可連接到其他計算部件,如集群中的計算節(jié)點。網(wǎng)絡(luò)通信的速度通常比前述其他通信方式慢得多:前端總線每秒可傳輸幾十吉比特,而網(wǎng)絡(luò)每秒只能傳輸幾十兆比特。
顯然,總線的主要屬性是速度:在給定時間內(nèi)可傳輸多少數(shù)據(jù)。這個屬性由兩個數(shù)值表征:一次能傳輸多少數(shù)據(jù)(總線寬度);每秒能傳輸多少次(總線頻率)。需要指出的是,一次傳輸?shù)臄?shù)據(jù)總是串行的:從存儲單元中讀取一個數(shù)據(jù)塊,并將其移到另一個地方。之所以用兩個數(shù)值來表征總線速度,是因為它們影響的計算方面不同:總線寬度很大時,可一次性傳輸所有相關(guān)的數(shù)據(jù),這對向量化代碼(以及從存儲單元中順序讀取數(shù)據(jù)的代碼)大有裨益,而對于需要從存儲單元中隨機讀取數(shù)據(jù)的代碼來說,低總線寬度和高頻率大有裨益。有趣的是,為改變這些屬性,計算機設(shè)計人員采用的方法之一是調(diào)整主板的物理布局:芯片離得越近,用于連接它們的導(dǎo)線就越短,而這有助于提高傳輸速度。另外,導(dǎo)線數(shù)量越多,總線寬度越大(從物理上說,總線就越寬)。
鑒于可根據(jù)應(yīng)用程序的性能要求調(diào)整接口,因此接口類型成百上千。圖1-3顯示了一些常見接口的速度。需要注意的是,該圖根本沒有提及連接的延遲——對數(shù)據(jù)請求做出響應(yīng)的時間。雖然延遲隨計算機而異,但每種接口類型都存在固有的基本延遲。

圖1-3 各種常見接口的連接速度[3]
[3] 這里的數(shù)據(jù)摘自O(shè)’Reilly網(wǎng)站。
- 多媒體CAI課件設(shè)計與制作導(dǎo)論(第二版)
- C++程序設(shè)計教程
- Implementing Modern DevOps
- Mastering ServiceStack
- Learning Spring 5.0
- PostgreSQL Cookbook
- C語言程序設(shè)計
- Visual Basic程序設(shè)計實驗指導(dǎo)(第二版)
- Extreme C
- Django 3.0應(yīng)用開發(fā)詳解
- FPGA嵌入式項目開發(fā)實戰(zhàn)
- Angular應(yīng)用程序開發(fā)指南
- Java EE輕量級解決方案:S2SH
- Python繪圖指南:分形與數(shù)據(jù)可視化(全彩)
- Learning Redux