序言
第1章 并行和分布式計(jì)算介紹
第2章 異步編程
第3章 Python的并行計(jì)算
第4章 Celery分布式應(yīng)用
第5章 云平臺部署Python
第6章 超級計(jì)算機(jī)群使用Python
第7章 測試和調(diào)試分布式應(yīng)用
第8章 繼續(xù)學(xué)習(xí)
本書示例代碼適用于Python 3.5及以上。
當(dāng)代第一臺數(shù)字計(jì)算機(jī)誕生于上世紀(jì)30年代末40年代初(Konrad Zuse 1936年的Z1存在爭議),也許比本書大多數(shù)讀者都要早,比作者本人也要早。過去的七十年見證了計(jì)算機(jī)飛速地發(fā)展,計(jì)算機(jī)變得越來越快、越來越便宜,這在整個(gè)工業(yè)領(lǐng)域中是獨(dú)一無二的。如今的手機(jī),iPhone或是安卓,比20年前最快的電腦還要快。而且,計(jì)算機(jī)變得越來越小:過去的超級計(jì)算機(jī)能裝下整間屋子,現(xiàn)在放在口袋里就行了。
這其中包括兩個(gè)重要的發(fā)明。其一是主板上安裝多塊處理器(每個(gè)處理器含有多個(gè)核心),這使得計(jì)算機(jī)能真正地實(shí)現(xiàn)并發(fā)。我們知道,一個(gè)處理器同一時(shí)間只能處理同一事務(wù);后面章節(jié)我們會(huì)看到,當(dāng)處理器快到一定程度,就可以給出同一時(shí)間進(jìn)行多項(xiàng)任務(wù)的假象。若要真正實(shí)現(xiàn)同一時(shí)間多任務(wù),就需要多個(gè)處理器。
另一項(xiàng)發(fā)明是高速計(jì)算機(jī)網(wǎng)絡(luò)。它首次讓無窮多的電腦實(shí)現(xiàn)了相互通訊。聯(lián)網(wǎng)的電腦可能處于同一地點(diǎn)(稱為局域網(wǎng)LAN)或分布在不同地點(diǎn)(稱為廣域網(wǎng)WAN)。
如今,我們都已熟悉多處理器/多核心計(jì)算機(jī),事實(shí)上,我們的手機(jī)、平板電腦、筆記本電腦都是多核心的。顯卡,或圖形處理器(GPU),往往是大規(guī)模并行機(jī)制,含有數(shù)百乃至上千個(gè)處理單元。我們周圍的計(jì)算機(jī)網(wǎng)絡(luò)無處不在,包括:Internet、WiFi、4G網(wǎng)絡(luò)。
本章剩余部分會(huì)探討一些定義。我們會(huì)介紹并行和分布式計(jì)算的概念。給出一些常見的示例。探討每個(gè)架構(gòu)的優(yōu)缺點(diǎn),和編程的范式。
在開始介紹概念之前,先澄清一些東西。在剩余部分中,除非特別指明,我們會(huì)交叉使用處理器和CPU核心。這在概念上顯然是不對的:一個(gè)處理器會(huì)有一個(gè)或多個(gè)核,每臺計(jì)算機(jī)會(huì)有一個(gè)或多個(gè)處理器。取決于算法和性能要求,在多處理器或單處理器多核的計(jì)算機(jī)上運(yùn)行可能會(huì)有速度上的不同,假定算法是并行的。然而,我們會(huì)忽略這些差異,將注意力集中于概念本身。
并行計(jì)算
并行計(jì)算的概念很多。本書提供一個(gè)簡潔的概念:
并行計(jì)算是同時(shí)使用多個(gè)處理器處理事務(wù)。
典型的,這個(gè)概念要求這些處理器位于同一塊主板,以區(qū)別于分布式計(jì)算。
分工的歷史和人類文明一樣久遠(yuǎn),也適用于數(shù)字世界,當(dāng)代計(jì)算機(jī)安裝的計(jì)算單元越來越多。
并行計(jì)算是有用且必要的原因很多。最簡單的原因是性能;如果我們要把一個(gè)冗長的計(jì)算分成小塊、打包給不同的處理器,就可以在相同時(shí)間內(nèi)完成更多工作。
或者,并行計(jì)算在處理一項(xiàng)任務(wù)時(shí),還可以向用戶呈現(xiàn)反饋界面。記住一個(gè)處理器同一時(shí)間只能處理一項(xiàng)任務(wù)。有GUIs的應(yīng)用需要將任務(wù)交付給另一個(gè)處理器的獨(dú)立線程,以讓另一個(gè)處理器能更新GUI,并對輸入進(jìn)行反饋。
下圖展示了這個(gè)常見的架構(gòu),主線程使用事件循環(huán)(Event Loop)處理用戶和系統(tǒng)輸入。需要長時(shí)間處理的任務(wù)和會(huì)阻塞GUI的任務(wù)會(huì)被移交給后臺或worker線程:

一個(gè)該并行架構(gòu)的實(shí)際案例可以是一個(gè)圖片應(yīng)用。當(dāng)我們將數(shù)碼相機(jī)或手機(jī)連接到電腦上時(shí),圖片應(yīng)用會(huì)進(jìn)行一系列動(dòng)作,同時(shí)它的用戶界面要保持交互。例如,應(yīng)用要將圖片從設(shè)備拷貝到硬盤上、建立縮略圖、提取元數(shù)據(jù)(拍攝日期及時(shí)間)、生成索引、最后更新圖片庫。與此同時(shí),我們?nèi)匀豢梢詾g覽以前傳輸?shù)膱D片,打開圖片、進(jìn)行編輯等等。
當(dāng)然,整個(gè)過程在單處理器上可能是順序依次進(jìn)行的,這個(gè)處理器也要處理GUI。這就會(huì)造成用戶界面反應(yīng)遲緩,整個(gè)應(yīng)用會(huì)變慢。并行運(yùn)行可以使這個(gè)過程流暢,提高用戶體驗(yàn)。
敏銳的讀者此時(shí)可能指出,以前的只有單處理器單核的舊電腦也可以(通過多任務(wù))同時(shí)處理多個(gè)事件。即使如今,也可以讓運(yùn)行的任務(wù)數(shù)超過計(jì)算機(jī)的處理器數(shù)目。其實(shí),這是因?yàn)橐粋€(gè)正在運(yùn)行的任務(wù)被從CPU移出(這可能是自發(fā)或被操作系統(tǒng)強(qiáng)制的,例如,響應(yīng)IO事件),好讓另一個(gè)任務(wù)可以在CPU上運(yùn)行。類似的中斷會(huì)時(shí)而發(fā)生,在應(yīng)用運(yùn)行中,各種任務(wù)會(huì)相繼獲得會(huì)被移出CPU。切換通常很快,這樣,用戶就會(huì)有計(jì)算機(jī)并行運(yùn)行任務(wù)的感覺。實(shí)際上,只有一個(gè)任務(wù)在特定的時(shí)間運(yùn)行。
通常在并行應(yīng)用中運(yùn)行的工具是線程。系統(tǒng)(比如Python)通常對線程有嚴(yán)格的限制(見第3章),開發(fā)者要轉(zhuǎn)而使用子進(jìn)程subprocess(通常的方法是分叉)。子進(jìn)程取代(或配合)線程,與主進(jìn)程同時(shí)運(yùn)行。
第一種方法是多線程編程(multithreaded programming)。第二種方法是多進(jìn)程(multiprocessing)。多進(jìn)程可以看做是多線程的變通。
許多情況下,多進(jìn)程要優(yōu)于多線程。有趣的是,盡管二者都在單機(jī)運(yùn)行,多線程是共享內(nèi)存架構(gòu)的例子,而多進(jìn)程是分布式內(nèi)存架構(gòu)的例子(參考本書后續(xù)內(nèi)容)。
分布式計(jì)算
本書采用如下對分布式計(jì)算的定義:
分布式計(jì)算是指同一時(shí)間使用多臺計(jì)算機(jī)處理一個(gè)任務(wù)。
一般的,與并行計(jì)算類似,這個(gè)定義也有限制。這個(gè)限制通常是要求,對于使用者,這些計(jì)算機(jī)可以看做一臺機(jī)器,進(jìn)而掩蓋應(yīng)用的分布性。本書中,我們更喜歡這個(gè)廣義的定義。
顯然,只有當(dāng)計(jì)算機(jī)之間互相連接時(shí),才可以使用分布式計(jì)算。事實(shí)上,許多時(shí)候,這只是對我們在之前部分的并行計(jì)算的概念總結(jié)。
搭建分布式系統(tǒng)的理由有很多。通常的原因是,要做的任務(wù)量太大,一臺計(jì)算機(jī)難以完成,或是不能在一定時(shí)間內(nèi)完成。一個(gè)實(shí)際的例子就是皮克斯或夢工廠的3D動(dòng)畫電影渲染。
考慮到整部電影要渲染的總幀數(shù)(電影兩個(gè)小時(shí),每秒有30幀),電影工作室需要將海量的工作分配到多臺計(jì)算機(jī)(他們稱其為計(jì)算機(jī)農(nóng)場)。
另外,應(yīng)用本身需要分布式的環(huán)境。例如,即時(shí)聊天和視頻會(huì)議應(yīng)用。對于這些應(yīng)用,性能不是最重要的。最關(guān)鍵的是,應(yīng)用本身要是分布式的。下圖中,我們看到一個(gè)非常常見的網(wǎng)絡(luò)應(yīng)用架構(gòu)(另一個(gè)分布式應(yīng)用例子),多個(gè)用戶與網(wǎng)站相連。同時(shí),應(yīng)用本身要與LAN中不同主機(jī)的系統(tǒng)(例如數(shù)據(jù)庫服務(wù)器)通訊:

另一個(gè)分布式系統(tǒng)的例子,可能有點(diǎn)反直覺,就是CPU-GPU。如今,顯卡本身就是很復(fù)雜的計(jì)算機(jī)。它們高并行運(yùn)行,處理海量計(jì)算密集型任務(wù),不僅是為了在顯示器上顯示圖像。有大量的工具和庫(例如NVIDIA的CUDA,OpenCL和OpenAcc)可以讓開發(fā)者對GPU進(jìn)行開發(fā),來做廣義計(jì)算任務(wù)。(譯者注:比如在比特幣中,使用顯卡編程來挖礦。)
然而,CPU和GPU組成的系統(tǒng)實(shí)際上就是一個(gè)分布式系統(tǒng),網(wǎng)絡(luò)被PCI總線取代了。任何要使用CPU和GPU的應(yīng)用都要處理數(shù)據(jù)在兩個(gè)子系統(tǒng)之間的移動(dòng),就像傳統(tǒng)的在網(wǎng)絡(luò)中運(yùn)行的應(yīng)用。
將現(xiàn)存的代碼移植到計(jì)算機(jī)網(wǎng)絡(luò)(或GPU)不是一件輕松的工作。要移植的話,我發(fā)現(xiàn)先在一臺計(jì)算機(jī)上使用多進(jìn)程完成,是一個(gè)很有用的中間步驟。我們會(huì)在第3章看到,Python有強(qiáng)大的功能完成這項(xiàng)任務(wù)(參考concurrent.futures模塊)。
一旦完成多進(jìn)程并行運(yùn)行,就可以考慮將這些進(jìn)程分拆給獨(dú)立的應(yīng)用,這就不是重點(diǎn)了。
特別要注意的是數(shù)據(jù),在哪里存儲、如何訪問。簡單情況下,共享式的文件系統(tǒng)(例如,UNIX的NFS)就足夠了;其余情況,就需要數(shù)據(jù)庫或是消息隊(duì)列。我們會(huì)在第4章中看幾個(gè)這樣的實(shí)例。要記住,真正的瓶頸往往是數(shù)據(jù)而不是CPU。
共享式內(nèi)存vs分布式內(nèi)存
在概念上,并行計(jì)算和分布計(jì)算很像,畢竟,二者都是要將總計(jì)算量分解成小塊,再在處理器上運(yùn)行。有些讀者可能會(huì)想,一種情況下,使用的處理器全部位于一臺計(jì)算機(jī)之內(nèi),另一種情況下,處理器位于不同的計(jì)算機(jī)。那么,這種技術(shù)是不是有點(diǎn)多余?
答案是,可能。正如我們看到的,一些應(yīng)用本身是分布式的。其它應(yīng)用則需要更多的性能。對于這些應(yīng)用,答案就可能是“有點(diǎn)多余”——應(yīng)用本身不關(guān)心算力來自何處。然而,考慮到所有情況,硬件資源的物理存放地點(diǎn)還是有一定意義的。
也許,并行和分布式計(jì)算的最明顯的差異就是底層的內(nèi)存架構(gòu)和訪問方式不同。對于并行計(jì)算,原則上,所有并發(fā)任務(wù)可以訪問同一塊內(nèi)存空間。這里,我們必須要說原則上,因?yàn)槲覀円呀?jīng)看到并行不一定非要用到線程(線程是可以訪問同一塊內(nèi)存空間)。
下圖中,我們看到一個(gè)典型的共享式內(nèi)存架構(gòu),四個(gè)處理器可以訪問同一內(nèi)存地址。如果應(yīng)用使用線程,如果需要的話,線程就可以訪問同一內(nèi)存空間:

然而,對于分布式應(yīng)用,不同的并發(fā)任務(wù)不能正常訪問同一內(nèi)存。原因是,一些任務(wù)是在這一臺計(jì)算機(jī)運(yùn)行,一些任務(wù)是在另一臺計(jì)算機(jī)運(yùn)行,它們是物理分隔的。
因?yàn)橛?jì)算機(jī)之間可以靠網(wǎng)絡(luò)通訊,可以想象寫一個(gè)軟件層(中間件),以一個(gè)統(tǒng)一的內(nèi)存邏輯空間呈現(xiàn)應(yīng)用。這些中間件就是分布式共享內(nèi)存架構(gòu)。此書不涉及這樣的系統(tǒng)。
下圖中,我們還有有四個(gè)CPU,它們處于共享內(nèi)存架構(gòu)中。每個(gè)CPU都有各自的私有內(nèi)存,看不到其它CPU的內(nèi)存空間。四臺計(jì)算機(jī)(包圍CPU和內(nèi)存的方框)通過網(wǎng)絡(luò)通訊,通過網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)傳輸:

現(xiàn)實(shí)中,計(jì)算機(jī)是我們之前講過的兩種極端情況的結(jié)合體。計(jì)算機(jī)網(wǎng)絡(luò)通訊就像一個(gè)純粹的分布式內(nèi)存架構(gòu)。然而,每臺計(jì)算機(jī)有多個(gè)處理器,運(yùn)行著共享式內(nèi)存架構(gòu)。下圖描述了這樣的混合式架構(gòu):

這些架構(gòu)有各自的優(yōu)缺點(diǎn)。對于共享式內(nèi)存系統(tǒng),在單一文件的并發(fā)線程中分享數(shù)據(jù)速度極快,遠(yuǎn)遠(yuǎn)快過網(wǎng)絡(luò)傳輸。另外,使用單一內(nèi)存地址可以簡化編程。
同時(shí),還要注意不要讓各個(gè)線程發(fā)生重疊,或是彼此改變參數(shù)。
分布式內(nèi)存系統(tǒng)擴(kuò)展性強(qiáng)、組建成本低:需要更高性能,擴(kuò)展即可。另一優(yōu)點(diǎn)是,處理器可以訪問各自的內(nèi)存,不必?fù)?dān)心發(fā)生競爭條件(競爭條件指多個(gè)線程或者進(jìn)程在讀寫一個(gè)共享數(shù)據(jù)時(shí),結(jié)果依賴于它們執(zhí)行的相對時(shí)間的情形)。它的缺點(diǎn)是,開發(fā)者需要手動(dòng)寫數(shù)據(jù)傳輸?shù)牟呗?,需要考慮數(shù)據(jù)存儲的位置。另外,不是所有算法都能容易移植到這種架構(gòu)。
阿姆達(dá)爾定律
本章最后一個(gè)重要概念是阿姆達(dá)爾定律。簡言之,阿姆達(dá)爾定律是說,我們可以盡情并行化或分布化計(jì)算,添加算力資源獲得更高性能。然而,最終代碼的速度不能比運(yùn)行在單處理器的單序列(即非并行)的組件要快。
更正式的,阿姆達(dá)爾定律有如下公式??紤]一個(gè)部分并行的算法,稱P為并行分量,S為序列分量(即非并行分量),P+S=100%。T(n)為運(yùn)行時(shí)間,處理器數(shù)量為n。有如下關(guān)系:

這個(gè)公式轉(zhuǎn)化成白話就是:在n個(gè)處理器上運(yùn)行這個(gè)算法的時(shí)間大于等于,單處理器上運(yùn)行序列分量的時(shí)間S*T(1)加上,并行分量在單處理器上運(yùn)行的時(shí)間P*T(1)除以n。
當(dāng)提高處理器的數(shù)量n,等式右邊的第二項(xiàng)變得越來越小,與第一項(xiàng)對比,逐漸變得可以忽略不計(jì)。此時(shí),這個(gè)公式近似于:

這個(gè)公式轉(zhuǎn)化成白話就是:在無限個(gè)處理器上運(yùn)行這個(gè)算法的時(shí)間近似等于序列分量在單處理器上的運(yùn)行時(shí)間S*T(1)。
現(xiàn)在,思考一下阿姆達(dá)爾定律的意義。此處的假定過于簡單,通常,我們不能使算法完全并行化。
也就是說,大多情況下,我們不能讓S=0。原因有很多:我們可能必須要拷貝數(shù)據(jù)和/或代碼到不同的處理器可以訪問的位置。我們可能必須要分隔數(shù)據(jù),將數(shù)據(jù)塊在網(wǎng)絡(luò)中傳輸??赡芤占胁l(fā)任務(wù)的結(jié)果,并進(jìn)行進(jìn)一步處理,等等。
無論原因是什么,如果不能使算法完全并行化,最終的運(yùn)行時(shí)間取決于序列分量的表現(xiàn)。并行化的程度越高,加速表現(xiàn)越不明顯。
題外話,完全并行通常稱作密集并行(Embarrassingly parallel),或者用政治正確的話來說,愉悅并行(pleasantly parallel),它擁有最佳的擴(kuò)展性能(速度與處理器的數(shù)量呈線性關(guān)系)。當(dāng)然,對此不必感到尷尬!不幸的是,密集并行很少見。
讓我們給阿姆達(dá)爾定律添加一些數(shù)字。假定,算法在單處理器耗時(shí)100秒。再假設(shè),并行分量為99%,這個(gè)數(shù)值已經(jīng)很高了。添加處理器的數(shù)量以提高速度。來看以下計(jì)算:

我們看到,隨著n的提高,加速的效果不讓人滿意。使用10個(gè)處理器,是9.2倍速。使用100個(gè)處理器,則是50倍速。使用1000個(gè)處理器,僅僅是91倍速。
下圖描述了倍速與處理器數(shù)量的關(guān)系。無論使用多少處理器,也無法大于100倍速,即運(yùn)行時(shí)間小于1秒,即小于序列分量運(yùn)行的時(shí)間。

阿姆達(dá)爾定律告訴我們兩點(diǎn):我們最快可以將倍速提高到多少;收益減少時(shí),何時(shí)應(yīng)該減少硬件資源的投入。
另一有趣的地方是阿姆達(dá)爾定律適用于分布式系統(tǒng)和混合并行-分布式系統(tǒng)。這時(shí),n等于所有計(jì)算機(jī)的處理器總數(shù)目。
隨著能接觸的系統(tǒng)的性能變得越來越高,如果能使用剩余性能,還可以縮短分布式算法運(yùn)行的時(shí)間。
隨著應(yīng)用的的執(zhí)行時(shí)間變短,我們就傾向于處理更復(fù)雜的問題。對于這樣的算法進(jìn)化,即問題規(guī)模的擴(kuò)大(計(jì)算要求的擴(kuò)大)達(dá)到可接受的性能時(shí),稱作古斯塔夫森定律。
混合范式
我們現(xiàn)在能買到的電腦大多是多處理器多核的,我們將要寫的分布式應(yīng)用就是要這樣的電腦上運(yùn)行。這使得我們可以既開發(fā)分布式計(jì)算,也可以開發(fā)并行式計(jì)算。這種混合分布-并行范式是如今開發(fā)網(wǎng)絡(luò)分布應(yīng)用的事實(shí)標(biāo)準(zhǔn)?,F(xiàn)實(shí)通常是混合的。
總結(jié)
這一章講了基礎(chǔ)概念。我們學(xué)習(xí)了并行和分布式計(jì)算,以及兩個(gè)架構(gòu)的例子,探討了優(yōu)缺點(diǎn)。分析了它們是如何訪問內(nèi)存,并指出現(xiàn)實(shí)通常是混合的。最后講了阿姆達(dá)爾定律,它對擴(kuò)展性能的意義,硬件投入的經(jīng)濟(jì)考量。下一章會(huì)將概念轉(zhuǎn)化為實(shí)踐,并寫Python代碼!
序言
第1章 并行和分布式計(jì)算介紹
第2章 異步編程
第3章 Python的并行計(jì)算
第4章 Celery分布式應(yīng)用
第5章 云平臺部署Python
第6章 超級計(jì)算機(jī)群使用Python
第7章 測試和調(diào)試分布式應(yīng)用
第8章 繼續(xù)學(xué)習(xí)