Linus Torvalds :忘掉那該死的并行吧!

>>>  技術話題—商業文明的嶄新時代  >>> 簡體     傳統

在 Avoiding ping pong上,Linus Torvalds以其一貫高雅的調調抨擊了“并行計算就是未來”的論調,并在原文和 Reddit上收獲了數百條評論。雖然事情最終也沒有一個結果,但是許多觀點確實值得借鑒。


Linus論點如下:


推崇并行只不過是浪費大家的時間,“并行更高效”這種理論純屬胡說八道。大容量緩存是高效的,如果缺少緩存,并行一些低等級微內核可以說是毫無意義,除下特定類型上大規模規則計算,比如圖形處理。


沒有人會回到過去,那些復雜的亂序運行內核不會消失。擴展不可能無休止的進行,人們需求更多的移動性,那些叫囂擴展到上千核心的論調純屬扯淡,無需理會。


是有多么奇葩的思維才能幻想出這些神奇等等并行算法的用武之地?!


對于并行來說,唯一的用武之地就是圖形計算和服務器端,而并行計算在這些領域確實也得到了大量的應用。但是沒有任何疑問,并行在其他領域毫無用武之地。


所以,忘掉并行吧,它永遠都不可能被大規模推廣。對于終端用戶來說,4核就差不多了,而在這個領域,如果不增加太多的能耗,你也無法塞入更多的內核。同時,也不會有智障去閹割內核,降低其大小和性能只為了多塞幾個。通常情況下,閹割內核只是為了降低功耗,因此這里也不會有那么多閹割的內核讓你使用。


因此,講究程序的并行性本質上就是錯的,它基于了一個錯誤的前提,同時也只是一個早該過時的時髦術語。


在圖形計算和服務器端之外,并行并不是萬金油。即使在未來全新的領域同樣如此,因為你根本承担不起。如果你期望做低功耗計算機視覺,我敢肯定你一定不會在GP CPU上編碼。你甚至不會去使用GPU,因為它的開銷太大了。大部分情況下,你可能會選擇一些特殊的硬件——可能會基于某些神經網絡模型。


放棄吧。“并行就是未來”的說法純屬胡說八道。


在看討論之前,我們首先看一下Linus以reference counting為例說明了并行的復雜性(該部分轉自CoolShell)


在Linus回復之前有人指出對象需要鎖機制的情況下,引用計數的原子性問題:


由于(對象)通過多線程方式及多種獲取渠道,一般而言它需要自身維護一個互斥鎖——否則引用計數就不要求是原子的,一個更高層次的對象鎖足矣。


而Linus不那么認為:


引用計數的問題在于你經常需要在對象數據上鎖保護之前完成它。


問題有兩種情況,它們鎖機制是完全不一樣的:


  • object *reference* 對象引用

  • object data 對象數據


對象數據保護一般是一個對象擁有一個鎖,假設你沒有海量擴展性問題,不然你需要一些外部大一點的鎖(極端的例子,一個對象一個全局鎖)。


但對象引用主要關于對象的尋找(移除或釋放),它是否在哈希鏈,一棵樹或者鏈表上。當對象引用計數降為零,你要保護的不是對象數據,因為對象沒有在其它地方使用,你要保護的是對象的尋找操作。


而且查詢操作的鎖不可能在對象內部,因為根據定義,你還不知道這是什么對象,你在嘗試尋找它。

 

因此一般你要對查詢操作上鎖,而且引用計數相對那個鎖來說是原子的(譯者注:查詢鎖不是引用計數所在的對象所有,不能保護對象引用計數,后面會解釋為何引用計數變更時其所在對象不能上鎖)。


當然這個鎖是充分有效的,現在假設引用計數是非原子的,但你常常不僅僅使用一種方式來查詢:你可能擁有其它對象的指針(這個指針又被其它對象的對象鎖給保護起來),但同時還會有多個對象指向它(這就是為何你第一時間需要引用計數的理由)。


看看會發生什么?查詢不止存在一個鎖保護。你可以想象走過一張對象流程圖,其中對象存在指向其它對象的指針,每個指針暗含了一次對象引用,但當你走過這個流程圖,你必須釋放源對象的鎖,而你進入新對象時又必須增加一次引用。


而且為了避免死鎖,你一般不能立即對新對象上鎖——你必須釋放源對象的鎖,否則在一個復雜流程圖里,你如何避免ABBA死鎖(譯者注:假設兩個線程,一個是A->B,另一個B->;A,當線程一給A上鎖,線程二給B上鎖,此時兩者誰也無法釋放對方的鎖)?

 

原子引用計數修正了這一點,當你從對象A到對象B,你會這樣做:

 

  • 對象A增加一次引用計數,并上鎖。

  • 對象A一旦上鎖,A指向B的指針就是穩定的,于是你知道你引用了對象B。

  • 但你不能在對象A上鎖期間給B上鎖(ABBA死鎖)。

  • 對象B增加一次原子引用計數。

  • 現在你可以扔掉對象A的鎖(退出對象A)。

  • 對象B的原子引用計數意味著即使給A解鎖期間,B也不會失聯,現在你可以給B上鎖。


看見了嗎?原子引用計數使這種情況成為可能。是的,你想盡一切辦法避免這種代價,比如,你也許把對象寫成嚴格順序的,這樣你可以從A到B,絕不會從B到A,如此就不存在ABBA死鎖了,你也就可以在A上鎖期間給B上鎖了。


但如果你無法做到這種強迫序列,如果你有多種方式接觸一個對象(再一次強調,這是第一時間使用引用計數的理由),這樣,原子引用計數就是簡單又理智的答案。


如果你認為原子引用計數是不必要的,這就大大說明你實際上不了解鎖機制的復雜性。


相信我,并發設計是困難的。所有關于“并行化如此容易”的理由都傾向于使用簡單數組操作做例子,甚至不包含對象的分配和釋放。


那些認為未來是高度并行化的人一成不變地完全沒有意識到并發設計是多么困難。他們只見過Linpack,他們只見過并行技術中關于數組排序的一切精妙例子,他們只見過一切絕不算真正復雜的事物——對真正的用處經常是非常有限的。(譯者注:當然,我無意借大神之口把技術宗教化。實際上Linus又在另一篇帖子中綜合了對并行的評價。)


哦,我同意。我的例子還算簡單,真正復雜的用例更糟糕。


我嚴重不相信未來是并行的。有人認為你可以通過編譯器,編程語言或者更好的程序員來解決問題,他們目前都是神志不清,沒意識到這一點都不有趣。


并行計算可以在簡化的用例以及具備清晰的接口和模型上正常工作。你發現并行在服務器上獨立查詢里,在高性能計算(High-performance computing)里,在內核里,在數據庫里。即使如此,人們還得花很大力氣才能使它工作,并且還要明確限制他們的模型來盡更多義務(例如數據庫要想做得更好,數據庫管理員得確保數據得到合理安排來迎合局限性)。

 

當然,其它編程模型倒能派上用場,神經網絡(neural networking)天生就是非常并行化的,你不需要更聰明的程序員為之寫代碼。

在未來,應用程序究竟會發展成什么樣?與現在有著非常大的區別?還是基本上相同,這里我們不妨看一下討論(更多討論見Reddit和Avoiding ping pong):


Martin Thompson:


一旦工作集的大小超過了緩存容量,更大的緩存毫無意義。在低延時領域,為了保證整個應用程序放到緩存,我們通常會不擇手段,但是這絕對不是主流。使用更大的頁,并讓L2支持這些更大的頁顯然比實際緩存大小更有意義,當下我們已經可以看到很多大內存應用程序運行在Haswell上。


對比使用并行,通常情況下使用cache friendly 或者cache oblivious(實際上是cache friendly的升級)數據結構顯然更具生產效率。時至今日,“如果把在Fork-Join和并行流上投入些許精力放到提供更好的通用數據結構上(比如Maps和Trees,cache friendly)是否會更劃算”這樣辯論已經不再是困擾。在所謂的“多核問題解決”上,對比FJ和并行流,主流應用程序顯然可以獲得更多的提升。在這里,并不是說FJ和并行流不是個好的解決方案,而是后者可以給投資帶來更多的回報。


在并行和并發上也有很多實際的用例,其中Servlet模型就是一個很好的例子,甚至是PHP之類在服務器端上的擴展。當然,在這之上,管道的構建也是一個更為直觀的模型。


當談到數據結構上的并發存取時,數據結構可變需要被單獨對待。如果數據結構是不可變的,或者支持無阻塞并發讀取,那么在并行上將很容易擴展,也很容易被推斷。并發修改任何有趣的遠程數據結構(更不用說完整模型),管理起來都是非常復雜和困難的。拋開復雜性,任何從多個寫入者到1個共享模型/狀態的并發更新都會存在限制,這點已經被Universal Scalability Law證明。在核心越來越多的情況下,在需要擴展的情況下,我們經常和自己開玩笑——多個寫入者到任何模型的更新是否是一個好主意。慶幸的是,在大多數開發的應用程序代碼中,查詢針對的模型通常都不會有變化。


基于產業并發存取共享狀態的需求,一個嚴重的后果產生:我們通常都會同步的進行這個過程,并在一個分布式的環境中傳播。在算法和方法設計時,我們需要擁抱異步方式以避免延時限制。通過異步方式,我們可以實現無阻塞訪問,而基于強制隔離,我們可以讓應用程序更好地執行,并擁有更好的彈性。帶寬以高速提升,延時將趨于平穩。


新一年我對平臺提供商的愿望清單是:基礎設施將有更好的cache friendly和immutable,同時還具備讓異步編程更容易的Append-Only 數據結構、更好的管道并發、無阻塞APIS(比如JDBC)、語言外延(比如支持state machines和continuations),以及可以做申明式查詢的語言外延(比如 LINQ[3] for C#就可以提升)。同時也不要介意允許Java那種低等級訪問,我們已經遠超越了在瀏覽器沙箱中寫程序的時代。


AntiProtonBoy:


通常情況下,我對Linus是非常不感冒的,但是公平來講,這次他說的確實很有道理。大量核心一般是用在大規模分布式應用系統中,比如說你想模仿一個神經網絡。而在這個情況,你肯定也不會使用20萬臺個人電腦。他只是說在用戶空間,30個小的核心并不會比4到8個高速核心快,因此并行化在這里并沒有什么優勢,也只有在遭遇瓶頸時才考慮到瓶頸。


Gabriele Svelto:


著眼當下移動領域,“足夠快”很可能并非優化的終點。現在大部分使用電池的計算設備都在致力讓用戶能夠獲得一個更快的感知速度,從而在總體上節省電量。在這方面,某些并行算法完全處于劣勢:在同等條件下,它們通過等價串行的方式,以增加計算(通常是通信)開銷為代價來換取更快的速度。在實踐中,并不是所有計算之外的開銷都是等價的,因此,你還需要分攤一些固定的開銷,不過整體更快的執行可能更加有效;但也正是這樣,衡量是否要增加某個負載的速度將需要考慮更多變數。


Jeft:


Linus的說法可以說對,也可以說不對。事實上,人們期待可以更有效利用并行硬件的途徑已經相當久了,所以不能把這個作為新事物來看。事實上我們需要的不僅是語言,如果你給它分配了太多工作,將從根本上挑戰語言的基本結構,我們需求的語言是在需要的情況下可以最簡單地并行,我們才剛剛開始。


Patrick Chase:


所有的一切都決定于容量和速度上的改變。當容量不足和(或者)算法集不穩定時,你使用的是商業硬件,而這十年的風格一直是GPU。當容量變高了,算法更穩定了,你開始考慮定制硬件(當下,一般復雜的ASIC定制大約是1000萬美元或者更高;結果就是,你可以通過數學來發現哪個更有意義)。


如果只是容量變高了,算法還有一部分不穩定,那么定制一個包含了固定功能硬件和可編程硬件(DSPs、GPUs等等)的ASIC則非常有意義。這也是為什么高通公司為所有的Snapdragons都添加了“Hexagon”。


Maynard Handley:


當下,我們甚至沒有開始程序員的再教育,讓他們可以用更好的方式做事(更好的意思是抽象更匹配并行編程)。我們的語言、API以及工具仍然很糟糕,就像使用Fortran來做遞歸和指針一樣。當下我們的工具并沒有重構,這也讓我們避免去關心某個函數調用鏈是否增加了一個新的參數等。


Patrick Chase:


針對Gabriele提出的“那些問題可以通過選擇不同的語言解決”,你說的對,但是在現實世界中根本不可行。對比10年前,并行技術在難易度和開銷上并沒有什么根本上的突破。沒有出現神奇的編譯器,沒有出現突破性的方法和語言,Amdahl法則并沒有得到實質性的緩解。


序列化性能一定程度上受到了半導體工藝的限制,在這里我沒看到任何微核心在equilibrium和optimum可以利用的因素。


因此,我覺得“anon”說的不錯:并行方案只在必要的時候選用,提升單核性能則在任何可能的情況下。雖然這不是一個很好的愿景,但是卻可以work。


Linus Torvalds:


我可以想象到人們在服務器領域已經使用上了60核心,但是我們不認為這是件值得推廣的事情。我認為,在服務器端增加更多的緩存和集成更多的IO同樣更具效率。


在客戶端方面,仍然存在一些類似工作站的負載可以使用16核心,我認為借助它們,美術家確實可以更快地做Photoshop和視頻編輯工作。但是從全局來看,這部分市場份額非常之小,從臺式電腦市場萎縮就可見一斑。


因此,市場的趨勢更應該是“4核心搭載大量的集成,既便宜又低功耗”。


但是,預測是困難的,特別是對未來,我們需要邊走邊看。


CSDN 2015-08-23 08:43:52

[新一篇] 為什么你讀了1000篇有關如何改變的文章卻從未改變?

[舊一篇] 為什么軟件工程師應該養成寫作的習慣
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表