大多數人根據直覺就知道,在系統間傳遞消息要比在單個系統上執行簡單計算更加耗時。不過,在共享同一塊內存的系統的線程間傳遞消息是否是也更加耗時,這點可就不必定了。本章主要關注共享內存系統中的同步和通訊的開銷,只涉及了一些共享內存並行硬件設計的皮毛,想了解更多信息的讀者,能夠翻看Hennessy和Patterson的經典教材最新版[HP95]。html
小問題:爲何並行軟件程序員須要如此痛苦地學習硬件的低級屬性?若是隻學習更高級些的抽象是否是更簡單,更好,更通用?linux
若是隻是粗略地掃過計算機系統規範手冊,很容易讓人以爲CPU的性能就像在一條清晰跑道上的賽跑,以下圖所示,老是最快的人贏得比賽。程序員
雖然有一些只限於CPU的基準測試可以讓CPU達到上圖中顯示的理想狀況,可是典型的程序不像跑道,更像是一條帶有障礙的道路。託摩爾定律的福,最近幾十年間CPU的內部架構發生了急劇的變化。在後面的章節中將描述這些變化。算法
在上世紀80年代初,典型的微處理器在處理下一條指令以前,至少須要取指,解碼和執行3個時鐘週期來完成本條指令。與之造成鮮然對比是,上世紀90年代末期和本世紀初的CPU能夠同時處理多條指令,經過一條很長的「流水線」來控制CPU內部的指令流,圖2.2顯示了這種差別。數據庫
帶有長流水線的CPU想要達到最佳性能,須要程序給出高度可預測的控制流。代碼主要在緊湊循環中執行的程序,能夠提供恰當的控制流,好比大型矩陣或者向量中作算術計算的程序。此時CPU能夠正確預測在大多數狀況下,代碼循環結束後的分支走向。在這種程序中,流水線能夠一直保持在滿狀態,CPU高速運行。segmentfault
另外一方面,若是程序中帶有許多循環,且循環計數都比較小;或者面向對象的程序中帶有許多虛對象,每一個虛對象均可以引用不一樣的實對象,而這些實對象都有頻繁被調用的成員函數的不一樣實現,此時CPU很難或者徹底不可能預測某個分支的走向。這樣CPU要麼等待控制流進行到足以知道分支走向的方向時,要麼乾脆猜想——因爲此時程序的控制流不可預測——CPU經常猜錯。在這兩種狀況中,流水線都是空的,CPU須要等待流水線被新指令填充,這將大幅下降CPU的性能,就像圖中的畫同樣。緩存
不幸的是,流水線沖刷並非惟一影響現代CPU運行性能的的障礙。下一節將講述內存引用帶來的危害。安全
在上世紀80年代,微處理器從內存讀取一個值的時間比執行一條指令的時間短。在2006年,一樣是讀取內存的時間,微處理器能夠在這段時間執行上百條甚至上千條指令。這個差別來源於摩爾定律使得CPU性能的增加速度大大超過內存性能的增加速度,也有部分是因爲內存大小的增加速度。好比,70年代的微型計算機一般帶有4KB主存(是的,是KB,不是MB,更別提GB了),訪問須要一個週期。到2008年,CPU設計者仍然能夠構造單週期訪問的4KB內存,即便是在幾GHz時鐘頻率的系統上。事實上這些設計者常常構造這樣的內存,但他們如今稱呼這種內存爲「0級cache」。網絡
雖然現代微型計算機上的大型緩存極大地減小了內存訪問延遲,可是隻有高度可預測的數據訪問模式才能讓緩存發揮最大效用。不幸的是,通常像遍歷鏈表這樣的操做的內存訪問模式很是難以預測——畢竟若是這些模式是可預測的,咱們也就不會被指針所困擾了,是吧?數據結構
所以,正如圖中顯示的,內存引用經常是影響現代CPU性能的重要因素。
到如今爲止,咱們只考慮了CPU在單線程代碼中執行時會遭遇的性能障礙。多線程會爲CPU帶來額外的性能障礙,咱們將在下面的章節中接着講述。
其中一種障礙是原子操做。原子操做的概念在某種意義上與CPU流水線上的一次執行一條的彙編操做衝突了。拜硬件設計者的精密設計所賜,現代CPU使用了不少很是聰明的手段讓這些操做看起來是原子的,即便這些指令實際上不是原子的。不過即便如此,也仍是有一些指令是流水線必須延遲甚至須要沖刷,以便一條原子操做成功完成。
原子指令對性能的影響見上圖。
不幸的是,原子操做一般只用於數據的單個元素。因爲許多並行算法都須要在更新多個數據元素時,保證正確的執行順序,大多數CPU都提供了內存屏障。內存屏障也是影響性能的因素之一,下一節將對它進行描述。
小問題:什麼樣的機器會容許對多個數據元素進行原子操做?
下面是一個簡單的基於鎖的臨界區。
spin_lock(&mylock); a = a + 1; spin_unlock(&mylock);
若是CPU沒有按照上述語句的順序執行,變量」a」會在沒有獲得「mylock」保護的狀況下加一,這確定和咱們取」a」的值的目的不一致。爲了防止這種有害的亂序執行,鎖操做原語必須包含或顯式或隱式的內存屏障。因爲內存屏障的做用是防止CPU爲了提高性能而進行的亂序執行,因此內存屏障幾乎必定會下降CPU性能,以下圖所示。
對多線程程序來講,還有個額外的障礙影響CPU性能提高——「Cache Miss」。正如前文提到的,現代CPU使用大容量的高速緩存來下降因爲較低的內存訪問速度帶來的性能懲罰。可是,CPU高速緩存事實上對多CPU間頻繁訪問的變量起反效果。由於當某個CPU想去更改變量的值時,極有可能該變量的值剛被其餘CPU修改過。在這種狀況下,變量存在於其餘CPU而不是當前CPU的緩存中,這將致使代價高昂的Cache Miss。以下圖所示。
緩存未命中能夠視爲CPU之間的I/O操做,這應該是代價最低廉的I/O操做之一。I/O操做涉及網絡、大容量存儲器,或者(更糟的)人類自己,I/O操做對性能的影響遠遠大於以前幾個章節提到的各類障礙,以下圖所示。
共享內存式的並行計算和分佈式系統式的並行計算的其中一個不一樣點是,共享內存式並行計算的程序通常不會處理比緩存未命中更糟的狀況,而分佈式並行計算的程序則極可能遭遇網絡通訊延遲。這兩種狀況的延遲均可看做是通訊的代價——在串行程序中所沒有的代價。所以,通訊的開銷佔執行的實際工做的比率是一項關鍵設計參數。並行設計的一個主要目標是儘量的減小這一比率,以達到性能和可擴展性上的目的。
固然,說I/O操做屬於性能障礙是一方面,說I/O操做對性能的影響很是明顯則是另外一方面。下面的章節將討論二者的區別。
本節將概述前一節列出的性能障礙的實際開銷。不過在此以前,須要讀者對硬件體系結構有一個粗略認識,下一小節將對該主題進行闡述。
上圖是一個粗略的八覈計算機系統概要圖。每一個管芯有兩個CPU核,每一個核帶有本身的高速緩存,管芯內還帶有一個互聯模塊,使管芯內的兩個核能夠互相通訊。在圖中央的系統互聯模塊可讓四個管芯相互通訊,而且將管芯與主存鏈接起來。
數據以「緩存線」爲單位在系統中傳輸,「緩存線」對應於內存中一個2的冪大小的字節塊,大小一般爲32到256字節之間。當CPU從內存中讀取一個變量到它的寄存器中時,必須首先將包含了該變量的緩存線讀取到CPU高速緩存。一樣地,CPU將寄存器中的一個值存儲到內存時,不只必須將包含了該值的緩存線讀到CPU高速緩存,還必須確保沒有其餘CPU擁有該緩存線的拷貝。
好比,若是CPU0在對一個變量執行「比較並交換」(CAS)操做,而該變量所在的緩存線在CPU7的高速緩存中,就會發生如下通過簡化的事件序列:
1. CPU0檢查本地高速緩存,沒有找到緩存線。
2. 請求被轉發到CPU0和CPU1的互聯模塊,檢查CPU1的本地高速緩存,沒有找到緩存線。
3. 請求被轉發到系統互聯模塊,檢查其餘三個管芯,得知緩存線被CPU6和CPU7所在的管芯持有。
4. 請求被轉發到CPU6和CPU7的互聯模塊,檢查這兩個CPU的高速緩存,在CPU7的高速緩存中找到緩存線。
5. CPU7將緩存線發送給所屬的互聯模塊,而且刷新本身高速緩存中的緩存線。
6. CPU6和CPU7的互聯模塊將緩存線發送給系統互聯模塊。
7. 系統互聯模塊將緩存線發送給CPU0和CPU1的互聯模塊。
8. CPU0和CPU1的互聯模塊將緩存線發送給CPU0的高速緩存。
9. CPU0如今能夠對高速緩存中的變量執行CAS操做了。
小問題:這是一個簡化後的事件序列嗎?還有可能更復雜嗎?
小問題:爲何必須刷新CPU7高速緩存中的緩存線?
一些在並行程序中很重要的常見操做開銷以下所示。該系統的時鐘週期爲0.6ns。雖然在現代微處理器上每時鐘週期retire多條指令並不常見,可是在表格的第三列,操做被標準化到了整個時鐘週期,稱做「比率」。關於這個表格,第一個須要注意的是比率的值都很大。
4-CPU 1.8GHz AMD Opteron 844系統
操做 | 開銷(ns) | 比率 |
---|---|---|
Clock period | 0. | 1.0 |
Best-case CAS | 37.9 | 63.2 |
Best-case lock | 65.6 | 109.3 |
Single cache miss | 139.5 | 232.5 |
CAS cache miss | 306.0 | 510.0 |
Comms fabric | 3,000 | 5,000 |
Global Comms | 130,000,000 | 216,000,000 |
最好狀況下的CAS操做消耗大概40納秒,超過60個時鐘週期。這裏的「最好狀況」是指對某一個變量執行CAS操做的CPU正好是最後一個操做該變量的CPU,因此對應的緩存線已經在CPU的高速緩存中了,相似地,最好狀況下的鎖操做(一個「round trip對」包括獲取鎖和隨後的釋放鎖)消耗超過60納秒,超過100個時鐘週期。這裏的「最好狀況」意味着用於表示鎖的數據結構已經在獲取和釋放鎖的CPU所屬的高速緩存中了。鎖操做比CAS操做更加耗時,是由於鎖操做的數據結構中須要兩個原子操做。
緩存未命中消耗大概140納秒,超過200個時鐘週期。須要在存儲新值時查詢變量的舊值的CAS操做,消耗大概300納秒,超過500個時鐘週期。想一想這個,在執行一次CAS操做的時間裏,CPU能夠執行500條普通指令。這代表了細粒度鎖的侷限性。
小問題:硬件設計者確定被要求過改進這種狀況!爲何他們知足於這些單指令操做的糟糕性能呢?
I/O操做開銷更大。一條高性能(也是高價的)光纖通訊,好比Infiniand或者其餘私有的 interconnects,它的通信延遲大概有3微秒,在這期間CPU能夠執行5000條指令。基於某種標準的通訊網絡通常須要一些協議的處理,這更進一步增長了延遲。固然,物理距離也會增長延遲,理論上光速繞地球一週須要大概130毫秒,這至關於2億個時鐘週期。
小問題:這些數字大的讓人發瘋!我怎麼才能記住它們?
最近幾年並行計算受到大量關注的主要緣由是摩爾定律的終結,摩爾定律帶來的單線程程序性能提高(或者叫「免費午飯」[Sut08])也結束了。本節簡短地介紹一些硬件設計者可能採用的辦法,這些辦法能夠帶來某種形式上的「免費午飯」。
不過,前文中概述了一些影響併發性的硬件障礙。其中對硬件設計者來講最嚴重的一個限制莫過於有限的光速。在一個1.8GHz的時鐘週期內,光只能在真空中傳播大約8釐米遠。在5GHz的時鐘週期內,這個距離更是降到3釐米。這個距離對於一個現代計算機系統的體積來講,仍是過小了點。
更糟糕的是,電子在硅中的移動速度比真空中的光慢3到30倍,好比,內存引用須要在將請求發送給系統的其餘部分前,等待查找本地緩存操做結束。此外,相對低速和高耗電的驅動器須要將電信號從一個硅制管芯傳輸到另外一個管芯,好比CPU和主存間的通訊。
不過,如下(包括硬件和軟件的)技術也許能夠改善這一狀況:
1. 3D集成,
2. 新材料和新工藝,
3. 用光來替換電子,
4. 專用加速器,
5. 已有的並行計算軟件。
在下面的小節中將會分別介紹這些技術。
不過,因爲時鐘邏輯級別形成的延遲是沒法經過3D集成的方式下降的,而且必須解決諸如生產、測試、電源和散熱等3D集成中的重大問題。散熱問題須要用基於鑽石的半導體來解決,鑽石是熱的良好導體,但倒是電的絕緣體。聽說生成大型單晶體鑽石仍然很困難,更別說將鑽石切割成晶圓了。另外,看起來上述這些技術不大可能讓性能出現指數級增加,如同某些人習慣的那樣。
聽說斯蒂芬·霍金曾經聲稱半導體制造商面臨兩個基本問題:(1)有限的光速,(2)物質的原子本質。半導體制造商頗有可能已經逼近這兩個限制,不過有一些研究報告和開發過程關注於如何規避這兩個基本閒置。
其中一個規避物質的原子本質的辦法是一種稱爲「high-K」絕緣體材料,這種材料容許較大的設備模擬較小型設備的電氣屬性。這種材料存在一些嚴重的生產困難,可是能讓研究的前沿再向前推動一步。另外一個比較奇異的規避方法,根據電子能夠存在於多個能級之上的事實,在電子中存儲多個二進制位。不過這種方法還有待觀察,肯定可否在生產的半導體設備中穩定工做。
還有一種稱爲「量子點」的規避方法,使得能夠製造體積小得多的半導體設備,不過該方法還處於研究階段。
雖然光速是一個很難跨越的限制,可是半導體設備更多的是受限於電子移動的速度,而非光速,在半導體材料中移動的電子速度僅是真空中光速的3%到30%。在半導體設備間用銅來鏈接是一種提高電子移動速度的方法,而且出現其餘技術讓電子移動速度接近光速的可能性是很大的。另外,還有一些實驗用微小的光纖做爲芯片內和芯片間的傳輸通道,由於在玻璃中的光速能達到真空中光速的60%還多。這種方法存在的一個問題是光電/電光轉換的效率,會產生電能消耗和散熱問題。
這也就是說,除開在物理學領域的基礎性進展之外,任何讓數據流傳輸速度出現指數級增加的想法都受限於真空中的光速。
用通用CPU來處理某個專門問題,一般會將大量的時間和能源消耗在一些與當前問題無關的事項上。好比,當對一對向量進行點積操做時,通用CPU通常會使用一個帶循環計數的循環(通常是展開的)。對指令解碼、增長循環計數、測試計數和跳轉回循環的開始處,這些操做在某種意義上來講都是無用功:真正的目標是計算兩個向量對應元素的乘積。所以,在硬件上設計一個專用於向量乘法的部件會讓這項工做作的既快速又節能。
這就是在不少商用微處理器上出現向量指令的緣由。這些指令能夠同時操做多個數據,能讓點積計算減小解碼指令消耗和循環開銷。
相似的,專用硬件能夠更有效地進行加密/解密、壓縮/解壓縮、編碼/解碼和許多其餘的任務。不過不幸的是,這種效率提高不是免費的。包含特殊硬件的計算機系統須要更多的晶體管,即便在不用時也會帶來能源消耗。軟件也必須進行修改以利用專用硬件的長處,同時這種專門硬件又必須足夠通用,這樣高昂的up-front設計費用才能攤到足夠多的用戶身上,讓專用硬件的價錢變得能夠承受。部分因爲以上經濟考慮,專用硬件到目前爲止只在幾個領域出現,包括圖形處理(GPU),矢量處理器(MMX、SSE和VMX指令),以及相對規模較小的加密領域。
不過,隨着摩爾定律帶來的單線程性能提高的結束,咱們能夠安全的預測:將來各類各樣的專用硬件會大大增長。
雖然多核CPU曾經讓計算機行業驚訝了一把,可是事實上基於共享內存的並行計算機已經商用了超過半個世紀了。這段時間足以讓一些重要的並行軟件登上舞臺,事實也確實如此。並行操做系統已經很是常見了,好比並行線程庫,並行關係數據庫管理系統和並行數值計算軟件。這些現有的並行軟件在解決咱們可能碰見的並行難題上已經走了很長一段路。
也許最多見的例子是並行關係數據庫管理系統。它和單線程程序不一樣,並行關係數據庫管理系統通常用高級腳本語言書寫,併發地訪問位於中心的關係數據庫。在如今的高度並行化系統中,只有數據庫是真正須要直接處理並行化的。所以它運用了許多很是好的技術。
操做的開銷表格中的比率值相當重要,由於它們限制了並行計算程序的效率。爲了弄清這點,咱們假設一款並行計算程序使用了CAS操做來進行線程間通訊。假設進行通訊的線程不是與自身而主要是與其餘線程通訊,那麼CAS操做通常會涉及到緩存未命中。進一步假設對應每一個CAS通訊操做的工做須要消耗300納秒,這足夠執行幾個浮點transendental函數了。其中一半的執行時間消耗在CAS通訊操做上!這也意味着有兩個CPU的系統運行這樣一個並行程序的速度,並不比單CPU系統運行一個串行執行程序的速度快。
在分佈式系統中結果還會更糟糕,由於單次通訊操做的延遲時間可能和幾千條甚至上百萬條浮點操做的時間同樣長。這就說明了會產生大量工做量的通訊操做應該儘量減小。
小問題:既然分佈式系統的通訊操做代價如此昂貴,爲何人們還要使用它?
並行算法必須將每一個線程設計成儘量獨立運行的線程。越少使用線程間通訊手段,好比原子操做、鎖或者其它消息傳遞方法,應用程序的性能和可擴展性就會更好。簡而言之,想要達到優秀的並行性能和可擴展性,就意味着在並行算法和實現中掙扎,當心的選擇數據結構和算法,使用現有的並行軟件和環境,或者將並行問題轉換成已經有並行解決方案存在的問題。
原文 Hardware and its habits
做者 Paul E. McKenney
譯者 謝寶友,魯陽,陳渝
修訂 SegmentFault
許可 Creative Commons Attribution-Share Alike 3.0 United States license