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

csdn發表於2015-01-10

  在 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)天生就是非常並行化的,你不需要更聰明的程式設計師為之寫程式碼。

  在未來,應用程式究竟會發展成什麼樣?與現在有著非常大的區別?還是基本上相同,這裡我們不妨看一下討論(更多討論見RedditAvoiding 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核心搭載大量的整合,既便宜又低功耗”。

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

相關文章