免費午餐已經結束——軟件歷史性地向併發靠攏

http://blog.csdn.net/hsutter/archive/2006/08/29/1136281.aspx

原文出處:http://www.gotw.ca/publications/concurrency-ddj.htm

譯文發表於《程序員》2006.11。

 

免費大餐不久就將結束。對此,你有何打算,做好下一步準備了麼?

對主要的處理器廠商以及架構,包括IntelAMDSparcPowerPC[譯註1]來說,改善CPU性能的傳統方法,如提升時鐘速度和指令吞吐量,基本已走到盡頭,現在開始向超線程和多核架構靠攏。而且這兩個特性(特別是多核)已經在部分芯片實現,如PowerPCSparc IVIntelAMD也將在2005年內趕上。2004In-Stat/MDR秋季處理器論壇[譯註2]的主題就是多核設備,很多公司都展示了改進和新研發的多核處理器。不過,要將2004年稱爲多核年,顯然還不夠理直氣壯。

多核將引領軟件研發發生基礎性變化,特別對接下來幾年裏那些面向一般應用、運行在PC和低端服務器上的應用軟件(在今天已經銷售出去的軟件裏佔有很大比例)而言。在這篇文章裏,我想就多核爲何突然對軟件產生重要影響,以及併發鉅變如何影響我們和我們未來編寫軟件方式的問題展開討論。

我可以這麼說:免費大餐已經結束一兩年了,但我們現在纔開始意識到這個問題。

 

免費的性能大餐

業界存在一個有趣的現象:“安迪送,比爾取。”[譯註3]無論處理器性能提升多少,軟件都有辦法迅速吞噬。CPU速度十倍於前,軟件就有十倍於前的活要幹(或者肆無忌憚猛增軟件的工作量,導致性能下降)。在過去幾十年裏,由於CPU、內存和硬盤特別是CPU廠商強力推進主流系統向更新更快的方向發展,大多數軟件不做版本升級,甚至原封不動,就可輕鬆而持續地享受處理器性能提升的成果。儘管時鐘速度不是衡量系統性能的唯一和最好的標尺,但其重要意義不容忽視。我們見證了CPU的發展歷史:從500MHz1GHz,然後再到2GHz,不斷提高。今天,主流計算機已經進入3GHz時代。

不過,有一個很關鍵的問題:這種提升模式什麼時候會走到盡頭?儘管莫爾定律預言了歷史上的指數式增長,但我們很清楚指數式增長不可能永遠維持,因爲硬件畢竟受物理極限約束;光速是不可能更快的[譯註4]。所以增長必然放緩,最後停滯。順便說明一點,莫爾定律的主要描述對象是晶體管集成密度,但在一些相關的領域,如時鐘速度方面,也出現了類似的指數式增長;甚至在別的領域有更快的增長速度,例如著名的數據存儲量爆炸。不過這些重要趨勢需要另一篇文章來分析了。

如果你是一個軟件開發人員,那麼你可能一直在免費享受桌面計算機性能提升的大餐。某些操作會成爲應用程序性能的瓶頸?“你過慮了”,我們對這樣的回答耳熟能詳,“未來處理器將更爲強勁,而現在的應用程序速度倒是日益被非CPU吞吐量和內存速度因素扼殺,比如I/O、網絡和數據庫等等。”真的是這樣麼?

要在過去,這的確沒錯。但在以後,就完全不對了。

我有兩個消息要告訴大家。第一個是好消息,處理器性能仍然會不斷提高。第二個則是壞消息,至少在短時間內,處理器性能的提升,不再能像以往那樣讓現在的應用程序繼續免費獲益。

過去30年裏,CPU設計者主要從三個方面提高CPU性能,頭兩個就是從線性執行流程上考慮的:

1、時鐘速度

2、執行優化

3、緩存

提升時鐘速度將增大單位時間的時鐘週期數。讓CPU跑得更快,就意味着能讓同樣工作或多或少更快完成。

優化指令執行,可以在每個時鐘週期內完成更多工作。目前的CPU中,一些指令被不同程度地做了優化,如管線、分支預測、同一時鐘週期內執行更多指令,甚至指令流再排序支持亂序執行等[譯註5]。引入這些技術的目的是讓指令流更好、更快執行,降低延遲時間,挖掘每一時鐘週期內芯片的工作潛能。

在這裏,有必要對指令再排序作個簡單說明。我剛纔提到的部分指令優化手段,其實已遠非普通意義上的優化。這些優化可能改變程序原意,造成程序不響應程序員的正常要求。這可是個大問題。CPU設計師都是心智健全且經過嚴格訓練的好同志,正常情況下,他們連蒼蠅都不願傷害,自然也無意破壞你的程序。而在最近幾年裏,儘管知道指令重組有破壞程序語義的風險,但爲了提升每個時鐘週期內的工作效率,他們已經習慣於積極開展這類有風險的優化工作。難道海德先生[譯註6]復活了?當然不是。這種積極性清楚表明,芯片設計師承受了交付速度更快CPU的巨大壓力;在這種壓力下,爲了讓軟件跑得更快,他們不得不冒改變程序意思,甚至應用崩潰的風險。拿兩個有名的例子來說——寫操作再排序和讀操作再排序[譯註7]。允許處理器對寫操作再排序是非常令人吃驚的,讓大多數程序員意外,一般來說這個特性必須關閉,因爲在寫操作被處理器武斷地再排序條件下,程序員很難保證程序正確執行。讀操作再排序也有明顯的問題,但大多數情況下這個特性是開啓的;因爲相對來說它更容易把握一些,而且人們對性能的要求,讓操作系統和操作環境設計師只能選擇讓程序員在一定程度吃點苦頭,畢竟,這比直接放棄性能優化機會的罪責小一些。

第三個是增大與RAM分離的片內高速緩存。RAM一直比CPU慢很多,因此讓數據近可能靠近處理器就很重要——當然那就是片內了[譯註8]。片內緩存持續飈升了很多年,現在的主流芯片商出售的CPU都帶有2M甚至更高的二級緩存。值得一提的是,今後,三種提升CPU性能的傳統手段裏,增加緩存將碩果僅存。我會在後面更詳細說明緩存的重要性。

 

我寫這麼多的意思是什麼呢?

最重要的是我們必須認識到,傳統性能提升方法與併發沒有直接關係。過去任何方法帶來的速度提升,無論是順序(非並行的單線程或單進程)、還是併發執行的程序,都能直接受益。這點很重要,我們目前大量的程序都是單線程的,而且在未來仍然有重要的存在價值。

當然,適當時候,我們重新編譯程序,可以利用CPU的新指令(如MMXSSE[譯註9])和新特性提升系統性能。但總的來說,即使放棄使用新指令和新特性,不做任何更改,老程序在新CPU也會跑得更快,讓人心花怒放。

曾經的世界是這般美好,可如今,她就要變了顏色。

 

爲什麼我們今天沒有10GHz芯片

       

其實,CPU性能提升在兩年前就開始碰壁,但大多數人到了最近纔有所覺察。

我這裏有份來自Intel的數據(當然你可以從其他廠商得到類似數據)。圖中反映了Intel芯片的時鐘速度和晶體管集成規模演變歷史。晶體管集成數至少就目前而言仍在繼續上升,但時鐘速度的情況就不同了。

我們從圖中可以看到,大概在2003年初,一路高歌猛進的CPU時鐘速度突然急剎車。受制於一些物理學問題,如散熱(發熱量太大且難以驅散)、功耗(太高)以及泄漏問題[譯註10]等,時鐘速度的提升已經越來越難。

你目前在工作站上用的CPU時鐘速度是多少?10GHz麼? 20018Intel芯片就達到2GHz,按照2003年前的CPU發展趨勢推算,到2005年初,我們就能擁有第一塊10GHzPentium芯片。但實際上沒辦到。而且情況好像越來越糟——我們根本就不知道到底在什麼時候這樣的芯片可以出現。

那麼放低期望,4GHz又如何呢?目前我們已到3.4GHz——那麼4GHz已經不遠了吧?唉,好像4GHz也遙不可及。可能你知道,Intel首先於2004年中將4GHz芯片的發佈時間推遲到2005年,而到了2004年秋季,則徹底取消了4GHz計劃[譯註11]。在本文寫作的同時,Intel宣佈計劃到2005年早期,實現到3.73GHz(即圖中的右上最高處)的微量提升。所以,至少就目前來說,時鐘速度的競賽實際上結束了,Intel和其他大多數處理器廠商將把旺盛的精力投入到多核等方向去。

也許,我們某天在主流PC裏能裝上4GHzCPU,但2005年別想。Intel實驗室裏的確已經有運行在更高速度的芯片——不過代價是驚人的,比如龐大數量的冷卻裝置。你想不久在你的辦公室裏就有這樣的冷卻設備,坐飛機的時候,就把它們放在你膝蓋上?別做夢了!

 

莫爾定律與新一代處理器

“沒有免費的午餐。”——摘自R. A. Heinlein的小說《The Moon Is a Harsh Mistress》。

 

莫爾定律玩完了?這個問題很有趣,嚴格地講,還不能這麼說。儘管和所有的指數式增長方式一樣,莫爾定律總有一天會走到盡頭,但最近這些年,還沒有這樣的危險。芯片工程師在榨取時鐘週期內剩餘價值時的確碰了壁,不過晶體管集成量仍在暴漲,所以從這個角度說,CPU近期仍將遵循莫爾定律,系統吞吐量繼續提高。

關鍵的變化,即本文的中心,是今後幾代處理器性能提升所走的道路將完全不同。同時,大多數現在的應用軟件將不再可能不作大規模重構,就能像過去那樣從處理器免費獲益。

接下來數年裏,新型芯片的性能提升將主要從三個方面入手,其中僅有一個沿襲是過去的:

1、超線程

2、多核

3、緩存

超線程,是指在單個CPU內,並行兩個或多個線程。超線程CPU已經發布了,支持並行執行一些指令。不過這種CPU還是存在短板,雖然給它增加了部分硬件如寄存器,但它和絕大多數普通CPU一樣,緩存、整數和浮點運算器仍然是唯一的。有資料表明,寫得較好的多線程應用,在超線程CPU上能獲得5%15%的性能提升;假設趨於理想狀態,即多線程程序寫得好到極點,那麼性能可以提高40%。不錯了,不過還是做不到成倍提升,而且對單線程應用毫無幫助。

多核,主要是指在一塊芯片上運行兩個或多個處理器。部分芯片如SparcPowerPC目前已經推出了多核版本。IntelAMD也計劃在2005年內初步實現,具體時間取決於它們的系統集成水平,功能則是一樣的。AMD初期在性能設計可能更具優勢,如更好的支持功能單片內集成,而Intel基本上就打算將兩顆Xeon膠合在一塊片子上了事。所以剛開始的時候,這種雙核芯片與一個真正的雙CPU系統在性能幾乎沒有差別,僅僅在價格上前者更爲便宜,畢竟它的主板上不需要兩個插槽和額外膠合件;另外,即便理想狀態,這種架構也無法達到雙倍速度,且無益於單線程應用,而只有寫得較好的多線程應用能得到好處。

最後一個是片內緩存,還能像預期那樣在近期繼續上升。三個方法中,僅有這個可以讓現有應用全面受益。片內緩存有令人難以置信的重要性和對大多數現有應用的超高價值,原因很簡單,那就是“空間就是速度”。CPU和主存交互的代價是巨大的,如果能避免,那就儘量不要和它打交道。在目前的系統裏,從主存獲取數據所花時間,通常是從緩存獲得數據的1050倍。很讓人吃驚吧,因爲很多人都以爲內存已經足夠快。其實這不過是與硬盤和網絡相比,而不是運行在更高速度的片內緩存。應用程序的工作與緩存間的適配程度,和我們是榮辱與共的。很多年來,不重構程序,僅僅提高緩存大小就拯救了現有應用,給它們帶來新生。軟件操縱的數據和爲新增功能而加入的代碼越來越多,性能敏感的操作必須繼續與緩存適配。套用經濟大蕭條時期老人常唸叨的一句話:“緩存爲王。”

順帶說件發生在我的編譯器小組的趣事,算是“空間就是速度”的一個佐證。32位和64位編譯器將同樣的代碼分別編譯成32位和64位程序。64CPU有多得多的寄存器和其他代碼優化特性,因此運行其上的64位編譯器先天的獲得極大性能提升。這當然很好。而數據的情況又如何呢?換到64位平臺上,內存中絕大部分數據的大小並未發生變化,唯一例外的就是指針,指針佔用了兩倍於以前的空間。因此,我們的編譯器和絕大多數32位應用相比,揮舞指針就費力得多。現在的指針耗用8個而不是4個字節,空間淨增加,結果我們發現64位編譯器的工作集[譯註12]大小顯著增加。工作集增大導致性能下降,差不多抵消了更快的CPU和更多寄存器帶來的性能優勢。就在我寫這篇文章的時候,6432位編譯器正以同樣的速度運行,儘管程序代碼完全一樣而且64位處理器先天能力更強。這就是“空間就是速度”。

緩存能,但超線程和多核CPU對現在的絕大多數應用,幾乎不會有任何影響。

綜上所述,硬件的變化到底會給軟件開發方式帶來怎樣的影響呢?你可能已經有了初步答案了。讓我們更深入研究,明白其厲害所在。

 

對軟件來說,這意味一次鉅變

上世紀90年代初,我們開始學着理解對象。在主流軟件開發領域裏,從結構化到面向對象編程是過去20甚至可以說30年來最重要的變革。這期間也發生了其他一些變化,例如近來誕生的的確讓人着迷的WebServices,但我們中絕大多數人在職業生涯裏從未有過見識像面向對象那樣基礎而深刻改變軟件開發方式的機會。

現在,機會來了。

也從現在開始,性能大餐就不再免費了。雖然託緩存增大的福,我們還能在半路上撿到普通的性能提升丸,但如果你希望你的應用程序在新的處理器裏能繼續獲得爆炸性的性能提升,那就需要你好好編寫併發程序了(通常是多線程的)。說比做容易啊,也不是所有問題都天生可以通過並行解決,而且併發編程的難度也是很大的。

肯定有人嚷嚷開了:“併發?並不是什麼新鮮玩意嘛!人們不早就在寫這樣的程序了麼?”是的,小部分程序員的確寫過。

別忘了,至少從上世紀60年代晚期的Simula開始,人們就在寫面向對象程序。但到了90年代,面向對象才成功發動革命並奪取統治地位。爲什麼呢?工業是受現實需求驅動的,爲了解決越來越大的問題,必須編寫越來越大的系統,這樣的系統需要越來越強勁的CPU和存儲設備支持,硬件系統也恰逢其時地逐步提供了這樣的支持。面向對象編程擅長抽象和依賴管理,所以成爲了開發經濟、可靠和可重用的大型軟件的必備利器。

併發編程差不多也有同樣漫長的歷史可以追溯,很早的時候,我們就開始編寫協程、管程[譯註13]以及其他與併發有關的東西。近10年來,我們也發現有越來越多的程序員在編寫併發應用(有多線程的,也有多進程的)系統。但是發生整體轉向性的鉅變,目前還不具備條件,需假以時日。現有大量的單線程應用,仍然有巨大的存在價值,這點我會在後面說明。

說點題外話,當前“下一次軟件開發革命”這樣的詞語多如牛毛,讓大家眼花繚亂,其實這是商家宣傳自己新技術所作的廣告,不要理睬它。新技術通常都很吸引人,有時候也很有用,但軟件開發方式的重大變革必然來源於在真正得到爆發式廣泛應用前就存在並經過多年緩慢成長、先進而穩定的技術。這個過程是繞不掉的。變革所依賴的基礎技術必須足夠成熟(包括有固定的廠商和工具支持);通常,這個成熟穩定過程至少要花費7年的時間,新技術在廣泛應用時纔不會有潛在的性能懸崖和陷阱。所以,像面向對象這樣的軟件開發變革,也必須在各項技術經過多年甚至幾十年磨礪後才能發生。即便在好萊塢,絕大多數的一夜成名,也仍然是多年努力後發生重大突破的表面象徵。

併發將是軟件開發史上的又一個重大變革。很多專家仍然在這個變革是否比面向對象還大的問題上爭論不休。這樣的爭論最好還是留給學問家吧。技術工作者最感興趣的是和麪向對象一樣,編程方式的變化程度、編程技術的複雜性和學習曲線問題。

 

併發之正反二面

併發技術(特別是多線程)在主流軟件裏大多應用在兩個方面。第一類是天然就彼此獨立的、邏輯上分離的控制流程,比如在我設計的數據庫複製服務器裏,每個複製Session都放在各自的線程裏,彼此完全獨立的,不會工作於同一條數據記錄上。第二類不像第一類那麼常見。爲了系統提升性能,像利用多CPU平臺的能力,挖掘應用程序其他部分的潛能等,我們也會編寫併發代碼。在我的數據庫複製服務器裏,多個獨立的線程在多CPU平臺上就工作得很好。

然而,併發編程也是要付出代價的。一些很明顯的問題相對來說無關緊要,比如鎖定。對資源的鎖定降低了系統的性能,但如果你能找到辦法最小化甚至消除資源共享,讓操作真正並行,從而明智得當地使用鎖,那麼從併發執行得到的收益,要遠大於在同步上蒙受的損失。

更重要的問題,大概就是並非所有應用都適用並行。這點我會在後面說明。

應該說,最大的問題,就是併發編程本身的難度了。程序員必須將腦子裏的編程模型轉化爲可靠的程序,這比實現順序執行的傳統程序難得多。

任何學習過併發的人都認爲自己已經理解併發,早早結束尋找他們認爲不可能但實際潛在的競爭衝突和他們其實仍沒鬧明白的問題。如果開發人員認真學習和思考併發編程,就會發現通過合理組織的內部測試能發現大多數的競爭衝突問題,這個時候,無論是在知識水平還是心情愉悅度上,他們都能達到一個新的高度。不過,除了經過理解爲什麼和怎麼進行真正壓力測試的行家測試過的、已經正式發佈的軟件,都會存在部分在普通測試中無法捕獲的潛伏併發問題。這些問題只有在真正的多處理器系統上纔會暴露出來,因爲在這樣的環境裏,多個線程不是在單處理器上切換,而是真正的併發運行,大量新問題就會涌現。而偏偏又有很多人自以爲已經真正理解如何編寫併發程序,真是讓人忐忑不安啊。我見過不少項目組,他們的程序在很多用戶那裏即便施以極端苛刻的壓力測試,都能出色工作,但某天一個用戶部署了真正的多處理器機器後,深層次的競爭衝突甚至程序崩潰問題馬上出現。CPU發展到今天,重構你的應用,讓它們多線程運行在真正的多核計算機上,的確像逼迫初學游泳的人一下子跳入深水——直達終點,似乎有點殘忍,但只有真正並行的環境,才能更容易暴露出你的問題。再說了,即使你組織了一個能真正編寫可靠並行代碼的團隊,也不能說就不會出現問題。例如,併發代碼運行可能非常安全,但(在多核的機子上)卻不比在單核的機子上跑得快。其典型原因就是線程未被合理分離,共享了單一資源,造成程序執行順序化。這類問題是相當微妙而複雜的。

結構化程序員學習面向對象(什麼是對象?什麼是虛函數?我如何使用繼承?知道“是什麼”和“怎麼辦”外,還得問一句:“如何保證理論上正確的設計在實踐中的正確性?”)是一個飛躍,同樣的,順序思維的程序員學習併發(什麼是競爭衝突?什麼是死鎖?它是怎麼出現的,我如何避免它?什麼樣的構造在我看來是並行的但實際上順序化了程序?在“是什麼”和“怎麼辦”外,還要回答同樣的問題:“如何保證理論上正確的設計在實踐中的正確性?”)也是一個飛躍。

現在的大量程序員並沒有真正理解併發,就像15年前大量程序員沒有真正理解對象一樣。但併發編程模式是可以學習的,尤其是我們要堅持基於消息和鎖的編程;一旦真正理解了併發,就會發現它並不比面向對象難多少,很容易覺得那是自然而然的。我們需要爲我們自己和我們的團隊做好訓練投資和時間的準備。

有必要說明一點,我在上面故意將併發編程模式限定在消息和鎖基礎上。其實也有在語言級就直接支持的無鎖編程,比如Java5和很流行的C++編譯器。但對於程序員來說,無鎖比有鎖併發編程難得多。大多數情況下,只需要系統和庫編寫者理解無鎖編程就行了,雖然事實上每個人都可以從無鎖系統和庫獲益。老實說,即便有鎖編程,也有點碰運氣的味道呢。

 

對我們來說這到底意味着什麼

好了,回到正題,將問題歸納一下。

1、我們已經討論清楚的、最重要結論是:如果應用程序想充分利用CPU吞吐增加量,那它們就必然日益需要併發,這種形勢逐漸明朗,並將在接下來的數年裏深入發展。Intel已經揚言未來他們會推出集成100顆內核的芯片,那麼單線程應用最多就只能利用這種芯片1/100的潛在生產力。“哦,性能沒那麼重要吧,計算機總是跑得越來越快”的論調已經變得天真而可疑,甚至在未來不久將完全錯誤。

目前,並不是所有的應用都需要(或者更準確的說,只有應用中重要的作業才需要)並行。像編譯這類的問題,是必須要考慮並行的,而其他則不一定。請看這個有趣的例子:一個女人需要九個月才能生產一個小孩,並不代表九個女人能花一個月生出一個孩子。你以前可能接觸過類似的推導,但有沒有感覺這個問題意猶未盡呢?如果有人再和你就此討論,我向你推薦一個刁鑽的問題:從這個命題你能斷定“女人-小孩”是一個非並行問題麼?通常,人們會下意識地認爲它天然就不是一個並行問題,但實際上並不完全是這樣。如果目的是生一個小孩,它的確是一個非並行問題;但如果是生產多個小孩,那麼它就是一個標準的並行問題了!所以說,目標不同,結論就大相徑庭。在考慮你的軟件是否和如何使用並行時,千萬別忘了面向目標原則。

2、可能不那麼明顯的結論是:CPU將很可能日益成爲應用程序性能的瓶頸。當然,不是所有應用都會這樣,目前還未明顯受制於CPU能力的應用在未來雖然可能受到CPU影響,CPU也不會一夜之間成爲它們的鐐銬。但“I/O、網絡和數據庫瓶頸”似乎快走到谷底,因爲在這些領域,條件仍在迅速改善(如GBWiFi網絡等等);而與此形成對比的是,CPU性能提升技術已走到峯點。請注意,我們的CPU目前在3GHz徘徊。所以,除了指望緩存在未來繼續擴大(這可真是一個大好消息),現在的單線程應用不太可能跑得更快。其他方面的性能提升手段,雖然未來還可能繼續發揮作用,不過已經無法和過去相提並論了。芯片設計師正在拼命尋找新辦法提高管線利用率,降低數據加載延遲,但在這些領域,長在低枝的果子早已被摘光。而應用程序新需求的增加不但不稍事休息,反而神經質地加速猛衝。我們只好逼迫程序做更多的事情,而程序則只有威逼CPU,壓垮它,除非程序能併發執行。

應對如此鉅變,我們現在有兩條路可以走。一是面向併發重構應用,二是勤儉持家,小心規劃代碼,讓它吃更少的食,幹更多的活。這就引出了第三個有趣的話題。

3提升程序效率、優化其性能將越來越重要,而不是反道而行。已經高度重視性能優化的語言將獲得重生,其他的語言趕緊奮起直追,朝着效率和優化努力吧。面對長期增長的需求,希望面向性能努力的語言和系統能爲我們分憂。

4、最後一點,編程語言和系統將不得不盡快做好面向併發的準備Java語言從一開始就支持併發編程,雖然還存在不少問題以致不得不發佈多個後續版本提升併發程序的正確性和效率。C++長期以來被用於編寫大型多線程系統,但它卻沒有對併發的標準支持(ISO C++標準甚至有意未提及線程[譯註14] ),因此,併發目前只能在一些不可移植的特定平臺和庫基礎上實現(而且實現還不夠完善,比如靜態變量只能初始化一次,這就要求編譯器自動加鎖,但很多C++的實現裏並不生成鎖)。另外,目前還存在多種並行編程標準,例如pthreadsOpenMP[譯註15] ,其中一些支持隱式並行,另一些顯式支持。讓編譯器分析單線程程序並自動生成並行代碼的隱式並行方式當然美妙而優雅,不過這類自動轉化工具的結果代碼質量還無法與人工編寫的顯式並行代碼媲美。目前的併發編程主要以鎖爲基礎,這種方式也很難把握,常常要賭幾分運氣。總之,我們迫切需要一個比目前語言提供的更高抽象層次的、統一的併發編程模式。關於這點,以後我還有更多的話要說。

 

總結

如果你以前對併發未加註意,那麼現在是時候了,仔細分析應用的設計,挑出現在和不久就可能過於依賴CPU能力的操作,研究這些部分如何從併發得益。你和你的團隊,現在也該深入學習和了解併發編程的要求、不足、風格和專業概念了。

少部分應用天然適用於並行,但大多數不是的。即便你知道程序受制於CPU的位置,可能也很難找到將這部分操作並行化的辦法。所有這些問題,要求我們加快對並行的思考和研究。隱式並行編譯器能幫點小忙,但不能指望太多,它不可能比得上盡你所能將順序化程序轉化爲顯式並行和多線程版本後的效果的。

感謝仍未停止的緩存擴大和管線少量優化,免費飯菜在今後還能有一點,不過從今天開始,餐館無償提供的只有小菜和飯後小點心了。菜譜上仍然有優質可口的魚片,但現在要享受它就得付費——設計精細化、代碼更復雜,而且要加倍測試。對於多數應用來說,這是個好消息,儘管要辛勤耕耘,但回報是豐厚的,因爲併發可以讓應用繼續從處理器能力暴增中充分受益。

 

 

譯註1

處理器發展歷史上,精簡指令集(RISCReduced Instruction Set Computer)陣營曾向微特爾(WintelMicrosoftIntel)陣營發動過三波聲勢浩大的微電腦盟主爭霸戰。

上世紀70年代以來,對處理器的要求更爲全面,不單是提升速度就能滿足,比如低耗能、小體積,加強數值運算、支持多媒體功能等。複雜指令集(CISCComplex Reduced Instruction Set Computer)的微指令多且長度不統一,造成解碼器線路複雜;加上受當時製造工藝的限制,如果在芯片上直接集成高速緩存和其他部件,體積和價格都將變得難以想象。

RISC應運而生。因爲RISC指令精簡、線路精簡,所以芯片體積和能耗降低,騰出空間也可容納更多寄存器;另外指令定長,通過硬件加速,還可提升效能。RISC學術界從一開始就分爲兩派:Berkeley RISC(伯克利大學RISC計劃)和Stanford MIPS(斯坦福大學MIPS計劃。MIPSMicroprocessor without Interlocked Pipeline Stages,無內部互鎖流水級的微處理器。我國龍芯即MIPS架構)。

Sun公司引進了RISC技術,在此基礎上制定了SparcScalable Processor Architecture,可擴展處理器體系結構)微處理器體系結構規範,並於1985年推出了相應處理器。1988年,Sun領頭組建了Sparc聯盟,口號是“RISC + UNIX vs CISC + DOS”,藉此發動第一次爭霸衝擊。後因市場遲遲未見銷量,加上Sun在技術上又留了一手,導致聯盟最後只剩Sun自己和德州儀器。

1990年,MIPS研究開花結果,開發團隊成立了同名公司MIPS,並於1991年建立了ACEAdvanced Computing Environment)聯盟,主要成員有Digital(迪吉多)、SGISilicon Graphics,視算科技)和Compaq(康柏)。Compaq當時市場低迷,且IntelCompaq展開了遊說,因此Compaq最先退出了聯盟;而Digital又忙於Alpha芯片的開發。主要成員離心離德,1992年,第二次聯盟以SGI收購MIPS草草收場。

IBM1975年開始精簡指令集芯片研究,也就是後來的PowerPC1992年,IBMMotorola(摩托羅拉)、Apple Computer(蘋果)宣佈合組Power聯盟。剛開始,Power聯盟引用學界評比論文,直指x86複雜指令集結構無法應付未來信息需求,而媒體也紛紛發難斥責x86電腦效能低劣,操作介面不便,都表示支持大膽改革的Power聯盟。形勢危機,當時Intel的總裁葛洛夫甚至認爲Intel已陷入死亡之谷,如果應對失措,“Intel”將成爲歷史名詞。Power聯盟洋洋得意。

但在接下來的三年裏,Power聯盟遲遲不能統一平臺標準,操作系統開發進度也嚴重滯後。到1994年,Intel推出了Pentium芯片,微軟的Windows 95也大功告成。儘管當時的Pentium遜於PowerPCWindows 95界面也不如Macintosh,但前二者相互支援,前後兼容,不斷改進,而後二者卻不夠統一,用戶沒有安全感。形勢急轉直下,Power聯盟大跌眼鏡。

第一波攻擊,Sun退回到工作站;第二波攻擊,SGI退回繪圖工作站;第三波攻擊至今,IBM還在服務端處理器領域靠PowerPC苦撐。

順帶說一句,IntelAMD主要走的都是CISC路線。但處理器發展到現在,各方面技術已經相互融合,不存在絕對CISC或者RISC的芯片了。

 

譯註2

In-Statwww.instat.com,全球著名的行業研究機構,Reed Business Information出版集團成員公司之一,Reed Elsevier的戰略組成部分;涉及半導體、電信和電子消費品等領域的研究、評估與預測。

MDRMicroDesign Resources,原屬美國Ziff-Davis電子出版集團,1999年被Reed Elsevier收購。

In-Stat/MDR主辦《Microprocessor Report》(《微處理器報告》)雜誌,三週刊;Microprocessor Forum(微處理器論壇)每年10月在加利福尼亞州San Jose舉行。

 

譯註3

原句爲:Andy giveth, and Bill taketh away.

AndyAndy Grove,安迪·格魯夫,1968年和羅伯特·諾宜斯(Robert Noyce)、戈登·摩爾(Gordon Moore)共同創立Intel

BillBill Gates,比爾·蓋茨,1975年和保羅·艾倫(Paul Allen)創立Microsoft

 

譯註4

大概是2001年看到過一則新聞,到網上搜了一下,內容大致如下:

澳大利亞教授韋伯領導的研究小組利用位於夏威夷的世界最大的天文望遠鏡“凱克”觀測17顆不同的類星體。由於這些類星體距離地球120億光年,它們在宇宙形成初期發出的光線到今天才抵達地球。在長途旅行中,部分光線被星系間的氣雲吸收。光線的吸收情況既能反映星系氣雲的性質,也能反映出光的變化情況,這其中就包括光的速度以及決定光速的光譜線精細結構常數。結果,韋伯等人發現,精細結構常數發生了微小的變化。從理論上說,這意味着光速有可能發生過改變。消息公佈後,不少物理學家對此發現持謹慎態度。韋伯及同事希望用位於智利的另一個大型天文望遠鏡來證實他們的結果,據稱要得出最終結論尚需23年的時間。

不過到目前爲止,似乎還沒看到他們的最終結論。

 

譯註5

管線:pipelining,或流水線。CPU的管線並不是數據輸入輸出使用的物理線路,而是指指令執行的流程。一條指令必須被分解爲多個執行步驟,每個步驟佔用一個時鐘週期。例如最基礎的管線是5級的:(1)取指令,(2)對指令譯碼,(3)演算出操作數,(4)執行指令,(5)將結果存儲到高速緩存。前三步由指令控制器(ICU)完成,後兩步由運算器(ALUFPU)完成。管線可以細化,例如蘋果的G4處理器採用了7級管線,AMD 2500+處理器10級,Intel公司的P310級,P420級,P4-E甚至高達31級。管線加長,則每級任務量減小,執行所需時間縮短,因此時鐘週期可以縮短,即時鐘速度加快。設管線爲N級,時鐘速度爲TMIPST百萬次/秒),那麼平均完成一條指令所花費時間爲N/T(當然要求芯片的確在每個週期內能完成各管線級的任務),因此理論上只要時鐘速度加快,則芯片處理能力上升。但問題是管線執行時總有出錯(如分支預測失敗)的可能,一旦出錯,整個管線就要全部清空,然後從第一級重新執行,在這種情況下,長管線的全部花費時間通常比短管線多。這就是部分AMD芯片比Intel相同甚至更高主頻的芯片實際速度要快的原因。

分支預測:branch prediction。解決管線中條件轉移引起管線停頓的問題。例如第一條指令是條件轉移,那麼需要等其判斷結果出來後才能執行下一條指令,分支預測可預測判斷結果,然後儘快執行其他指令,從而不致管線停頓。當然預測也有出錯的時候,預測失敗將導致管線清空,從頭執行。目前預測準確度可達90%,進一步提高分支預測準確率是正在研究的重要課題。

亂序執行:out-of-order execution。解決管線中指令相關引起管線停頓的問題。後序指令需要正在執行指令的結果,因此無法立即處理後序指令,這叫做指令相關,造成其他處理單元的停頓,白白損失時鐘週期。解決辦法是立即找出其他不相關指令來執行,最後由重新排列單元將各執行單元的結果按原來指令順序重新排列。很顯然,亂序執行是有風險的。

 

譯註6

出自美國電影《化身博士》(Dr. Jekyll and Mr. Hyde,有19311941兩個版本)。故事講述哲基爾醫生相信每個人都同時擁有兩極化的個性——好的一面與邪惡的一面。如果將這兩種個性分開成爲截然不同的兩個人,這兩個靈魂都將獲得釋放。他隨後成功地用化學實驗將自身邪惡的一面轉化成爲海德先生,此先生犯下了可怕的罪行。但當他想要停止用藥時,卻驚恐的發現一切爲時已晚……

 

譯註7

/寫操作再排序都屬亂序執行。看下面一段原始指令代碼:

(p1)br label              //分支判斷。若爲true則跳轉到label,否則繼續執行

ld8 r9 r5             //r5所指地址空間讀取8個字節到r9

add r2 r9 r3              //r9r3中值求和,並存入r2

其中ldloadrregister

因爲load操作較耗時間,通常花費幾個時鐘週期才能完成。因此從提高效率的角度看,應該在處理器空閒的情況下,儘早加載此操作。比如簡單再排序優化後:

ld8 r9 r5

(p1)br label

add r2 r9 r3

如果分支判斷結果爲false,流程發生跳轉,那麼可以直接捨棄r9的結果值,即ld8白做了;但如果未發生跳轉,則ld儘早執行,提高了流程整體效率。

當然,CPU實際工作遠比上面例子複雜。比如將ld提前,但如果ld失敗怎麼辦?有依賴關係的指令呢,能否亂序?在多線程應用裏,亂序還可能引起其他一些問題,比如Java中的Double-Checked Locking失敗

不光CPU支持亂序執行,現在的很多編譯器也開始做亂序優化,而且重心有逐漸從硬件轉到軟件的趨勢。

有兩篇資料可以參看:Scaling Itanium® Architecture for Higher Performance(特別是其中的如何處理指令依賴值得了解)和Verified Optimizations for the Intel IA-64 Architecture

 

譯註8

片內,英文爲on the dieOn-DieDie:裸芯;Chip:包裝後的芯片。類似有On-ChipOff-Die/Chip

 

譯註9

MMXMultiMedia eXtension,多媒體擴展。Intel19963月份正式公佈了MMX技術的細節,並於19971月正式向全球推出基於MMX技術的166MHz200MHz Pentium芯片,AMD也幾乎在同時推出了支持MMX技術的第六代處理器AMD K6

MMX技術是Intel針對×86體系的一次重大擴充,使計算機同多媒體相關任務的綜合處理能力提高了1.52倍。它不僅是Intel i386面世以來對CPU體系結構的一次顯著改進,同時也是IT界對多媒體數據處理等專用芯片及功能板卡的一次成功挑戰。

SSEStreaming SIMD(單指令多數據) Extensions,是Intel針對AMD K6-2引入的“3D NOW!”技術,於l999年在Pentium3中引入的SIMD擴展指令集,業界也稱爲MMX2,在多媒體數據處理(特別是3D)和浮點運算等能力上全面加強。

 

譯註10

目前頻率最高的處理器是Intel P4 570J3.8GHz,上升非常緩慢。

CPU頻率越高,所需電能和發熱量越多;而晶體管越小,耗電和熱量越低。製造工藝進步,可能讓晶體管更小,從而讓CPU在相同或一定程度內提高的能耗下達到更高頻率。從這個角度說,提升CPU頻率的瓶頸是製造工藝。

於是,Intel推出了了90nm工藝的Prescott核心Pentium4,其理論頻率將能達到6GHz!然而世事難料,在90nm工藝晶體管裏,由於電介質厚度太低無法阻擋電子的穿越,造成了嚴重的電流泄漏問題,隨之帶來的就是大量的電能消耗和廢熱。如果強行提升頻率,則發熱激增,CPU經不起如此的高燒。

泄漏電流問題並非不可解決,但絕不能在短時間辦到。至此,芯片廠商提升系統性能的思路開始發生重大變化,即轉向多核。

有關電流泄漏和應對策略的詳情,可參看:NetBurst的繼承者 Core微處理器架構技術解析

 

譯註11

IDF05Intel Developer Forum 2005)上。Intel首席執行官Craig Barrett就取消4GHz芯片計劃一事,半開玩笑當衆單膝下跪致歉。

 

譯註12

Working Set,記錄了操作系統爲進程提交的內存的總量。

 

譯註13

併發編程語言(Concurrent Language)中的術語。

協同程序(coroutines),或協程。用以實現協作式多任務,於上世紀60年代提出。同屬一個協程的多個進程,在同一時刻只能有一個處於運行狀態。協程屬於一種併發進程創建方式,其他方式還有Fork/joinCobegin/coend和進程顯式申明(Process declarations)等。

併發進程之間的通信方式主要有兩種方式:變量共享(shared variables)和消息傳遞(message passing)。其他還包括抽象於更高層次的遠程過程調用(remote procedure callRPC)等。

通信就離不開同步。同步方法主要包括:信號量(semaphores)、條件臨界區(conditional critical regions)、管程(monitors)、互斥(mutual exclusion)、路徑表達式(path expressions)、原子事務(atomic transactions)和彙集(rendezvous)等。

其中的管程是位於低級同步控制手段之上的一種對象化管理工具。信號量的使用是無結構的,很不方便;條件臨界區相對於信號量更結構化,但同步控制代碼仍然非常分散,不利於管理。因此引入了管程,它實現了共享資源的集中管理,封裝了共享資源以及施於其上的操作。

 

譯註14

因爲某些原因(如競爭條件下靜態變量初始化問題),線程還未被列入ISO C++標準。目前在不同的平臺上,都有線程的專門實現,短時間內難以完全統一。

不過Boost線程庫目前差不多具有準標準身份。

 

譯註15

並行編程中必須考慮的兩個問題是被處理數據和任務間通訊。經過用戶的選擇與市場的淘汰,現在的並行編程標準基本上趨向以下三種:

1、數據並行。特點,各任務處理的數據彼此分離,任務間通過消息傳遞進行通訊;數據分離和消息傳遞工作由編譯器完成。

HPFHigh Performance Fortran,高性能Fortran)是典型的數據並行編程語言。因爲目前的編譯器技術對實際應用中各種不規則問題的解決方案仍不夠理想,加上專注於數據並行,因此HPF未獲廣泛應用。

2、消息傳遞。特點,各任務處理的數據彼此分離,任務間通過消息傳遞進行通訊;數據分離和消息傳遞工作由程序員和用戶完成,因此對程序員要求很高。這種模式非常適用於消息傳遞的體系結構(如機羣系統),用戶和程序員主要需考慮的是通訊同步和通訊性能問題。

並行虛擬機(PVMParallel Virtual Machine)和消息傳遞接口(MPIMessage Passing Interface)是兩種廣泛使用的消息傳遞並行編程標準。其中PVM側重異構環境下的可移植性和互操作性;MPI更強調性能,但在異構環境下有不同的實現。幾乎所有的高性能計算系統都支持PVMMPI

3、共享內存。特點,各任務處理的數據實現內存共享,任務間也通過共享數據實現通訊;數據共享可由程序員或編譯器完成。共享內存並行編程主要應用在對稱多處理器(SMP Symmetric Multi Processors)系統上。

OpenMPOpen MultiProcessingX3H5發展而來)和PThreadPOSIX Thread)都是共享內存並行編程的實現。

OpenMP1993年建立的X3H5標準發展而來,目前已成共享內存並行編程的實際工業標準,得到DECIntelIBMSun等廠商廣泛支持。它ForthanC/C++得到了實現,主要支持隱式並行編程,即編譯器實現並行。

PThread主要在Unix系統上使用。Unix的實現系統很多,比如LinuxFreeBSDSolarisMac OS X等。要在衆多“類UNIX”上開發跨平臺的多線程應用,絕非易事,因此制定了POSIX Thread標準。David R. ButenhofBoost庫發起者之一,ISO C++標準委員會成員)的《Programming with POSIX Threads》這本書,可以說是Unix上編寫多線程應用的必備參考書。對其他平臺並行程序開發也有很高參考價值。

總的來說,共享內存並行編程與目前大多數的多線程程序員思維習慣最爲接近,是程序員從單核轉向多核系統需付代價最小的方案。但專家仍有不同意見,比如Herb Sutter就不看好OpenMP,因爲共享內存並行編程本質上並沒有太多改進,仍然依賴數據資源的鎖定,這會帶來性能問題。消息傳遞並行有性能優勢,但對程序員的要求又太高了。所有這些難題,還需要研究並行和各種標準、庫的專家繼續努力解決。

發佈了31 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章