天天看點

Project Tungsten:讓Spark将硬體性能壓榨到極限(轉載)

Tungsten項目将是Spark自誕生以來核心級别的最大改動,以大幅度提升Spark應用程式的記憶體和CPU使用率為目标,旨在最大程度上壓榨新時代硬體性能。Project Tungsten包括了3個方面的努力:

Memory Management和Binary Processing:利用應用的語義(application semantics)來更明确地管理記憶體,同時消除JVM對象模型和垃圾回收開銷。

Cache-aware computation(緩存友好的計算):使用算法和資料結構來實作記憶體分級結構(memory hierarchy)。

代碼生成(Code generation):使用代碼生成來利用新型編譯器和CPU。

為什麼CPU會成為新的瓶頸?這裡存在多個問題:首先,在硬體配置中,IO帶寬提升的非常明顯,比如10Gbps網絡和SSD存儲(或者做了條文化處理的HDD陣列)提供的高帶寬;從軟體的角度來看,通過Spark優化器基于業務對輸入資料進行剪枝,當下許多類型的工作負載已經不會再需要使用大量的IO;在Spark Shuffle子系統中,對比底層硬體系統提供的原始吞吐量,序列化和哈希(CPU相關)成為主要瓶頸。從種種迹象來看,對比IO,Spark當下更受限于CPU效率和記憶體壓力。

1. Memory Management和Binary Processing

在JVM上的應用程式通常依賴JVM的垃圾回收機制來管理記憶體。毫無疑問,JVM絕對是一個偉大的工程,為不同工作負載提供了一個通用的運作環境。然而,随着Spark應用程式性能的不斷提升,JVM對象和GC開銷産生的影響将非常緻命。

一直以來,Java對象産生的開銷都非常大。在UTF-8編碼上,簡單如“abcd”這樣的字元串也需要4個位元組進行儲存。然而,到了JVM情況就更糟糕了。為了更加通用,它重新定制了自己的儲存機制——使用UTF-16方式編碼每個字元(2位元組),與此同時,每個String對象還包含一個12位元組的header,和一個8位元組的哈希編碼,我們可以從 Java Object Layout工具的輸出上獲得一個更清晰的了解:

<a></a>

毫無疑問,在JVM對象模型中,一個4位元組的字元串需要48位元組的空間來存儲!

JVM對象帶來的另一個問題是GC。從高等級上看,通常情況下GC會将對象劃分成兩種類型:第一種會有很高的allocation/deallocation(年輕代),另一種的狀态非常穩定(年老代)。通過利用年輕代對象的瞬時特性,垃圾收集器可以更有效率地對其進行管理。在GC可以可靠地估算對象的生命周期時,這種機制可以良好運作,但是如果隻是基于一個很短的時間,這個機制很顯然會遭遇困境,比如對象忽然從年輕代進入到年老代。鑒于這種實作基于一個啟發和估計的原理,性能可以通過GC調優的一些“黑魔法”來實作,是以你可能需要給JVM更多的參數讓其弄清楚對象的生命周期。

然而,Spark追求的不僅僅是通用性。在計算上,Spark了解每個步驟的資料傳輸,以及每個作業和任務的範圍。是以,對比JVM垃圾收集器,Spark知悉記憶體塊生命周期的更多資訊,進而在記憶體管理上擁有比JVM更具效率的可能。

為了扭轉對象開銷和無效率GC産生的影響,我們引入了一個顯式的記憶體管理器讓Spark操作可以直接針對二進制資料而不是Java對象。它基于sun.misc.Unsafe建立,由JVM提供,一個類似C的記憶體通路功能(比如explicit allocation、deallocation和pointer arithmetics)。此外,Unsafe方法是内置的,這意味着,每個方法都将由JIT編譯成單一的機器指令。

在某些方面,Spark已經開始利用記憶體管理。2014年,Databricks引入了一個新的基于Netty的網絡傳輸機制,它使用一個類jemalloc的記憶體管理器來管理所有網絡緩沖。這個機制讓Spark shuffle得到了非常大的改善,也幫助了Spark創造了新的世界紀錄。

新記憶體管理的首次亮相将出現在Spark 1.4版本,它包含了一個由Spark管理,可以直接在記憶體中操作二進制資料的hashmap。對比标準的Java HashMap,該實作避免了很多中間環節開銷,并且對垃圾收集器透明。

當下,這個功能仍然處于開發階段,但是其展現的初始測試行能已然令人興奮。如上圖所示,我們在3個不同的途徑中對比了聚合計算的吞吐量——開發中的新模型、offheap模型、以及java.util.HashMap。新的hashmap可以支撐每秒超過100萬的聚合操作,大約是java.util.HashMap的兩倍。更重要的是,在沒有太多參數調優的情況下,随着記憶體利用增加,這個模式基本上不存在性能的衰弱,而使用JVM預設模式最終已被GC壓垮。

在Spark 1.4中,這個hashmap可以為DataFracmes和SQL的聚合處理使用,而在1.5中,我們将為其他操作提供一個讓其利用這個特性的資料結構,比如sort和join。毫無疑問,它将應用到大量需要調優GC以獲得高性能的場景。

2. Cache-aware computation(緩存友好的計算)

在解釋Cache-aware computation之前,我們首先回顧一下“記憶體計算”,也是Spark廣為業内知曉的優勢。對于Spark來說,它可以更好地利用叢集中的記憶體資源,提供了比基于磁盤解決方案更快的速度。然而,Spark同樣可以處理超過記憶體大小的資料,自動地外溢到磁盤,并執行額外的操作,比如排序和哈希。

類似的情況,Cache-aware computation通過使用 L1/ L2/L3 CPU緩存來提升速度,同樣也可以處理超過寄存器大小的資料。在給使用者Spark應用程式做性能分析時,我們發現大量的CPU時間因為等待從記憶體中讀取資料而浪費。在 Tungsten項目中,我們設計了更加緩存友好的算法和資料結構,進而讓Spark應用程式可以花費更少的時間等待CPU從記憶體中讀取資料,也給有用工作提供了更多的計算時間。

我們不妨看向對記錄排序的例子。一個标準的排序步驟需要為記錄儲存一組的指針,并使用quicksort 來互換指針直到所有記錄被排序。基于順序掃描的特性,排序通常能獲得一個不錯的緩存命中率。然而,排序一組指針的緩存命中率卻很低,因為每個比較運算都需要對兩個指針解引用,而這兩個指針對應的卻是記憶體中兩個随機位置的資料。

那麼,我們該如何提高排序中的快取區域性?其中一個方法就是通過指針順序地儲存每個記錄的sort key。舉個例子,如果sort key是一個64位的整型,那麼我們需要在指針陣列中使用128位(64位指針,64位sort key)來儲存每條記錄。這個途徑下,每個quicksort對比操作隻需要線性的查找每對pointer-key,進而不會産生任何的随機掃描。希望上述解釋可以讓你對我們提高快取區域性的方法有一定的了解。

這樣一來,我們又如何将這些優化應用到Spark?大多數分布式資料處理都可以歸結為多個操作組成的一個小清單,比如聚合、排序和join。是以,通過提升這些操作的效率,我們可以從整體上提升Spark。我們已經為排序操作建立了一個新的版本,它比老版本的速度快5倍。這個新的sort将會被應用到sort-based shuffle、high cardinality aggregations和sort-merge join operator。在2015年底,所有Spark上的低等級算法都将更新為cache-aware,進而讓所有應用程式的效率都得到提高——從機器學習到SQL。

3. 代碼生成

大約在1年前,Spark引入代碼生成用于SQL和DataFrames裡的表達式求值(expression evaluation)。表達式求值的過程是在特定的記錄上計算一個表達式的值(比如age &gt; 35 &amp;&amp; age &lt; 40)。當然,這裡是在運作時,而不是在一個緩慢的解釋器中為每個行做單步調試。對比解釋器,代碼生成去掉了原始資料類型的封裝,更重要的是,避免了昂貴的多态函數排程。

在之前的博文中,我們闡述了代碼生成可以加速(接近一個量級)多種TPC-DS查詢。當下,我們正在努力讓代碼生成可以應用到所有的内置表達式上。此外,我們計劃提升代碼生成的等級,從每次一條記錄表達式求值到向量化表達式求值,使用JIT來開發更好的作用于新型CPU的指令流水線,進而在同時處理多條記錄。

在通過表達式求值優化内部元件的CPU效率之外,我們還期望将代碼生成推到更廣泛的地方,其中一個就是shuffle過程中将資料從記憶體二進制格式轉換到wire-protocol。如之前所述,取代帶寬,shuffle通常會因資料系列化出現瓶頸。通過代碼生成,我們可以顯著地提升序列化吞吐量,進而反過來作用到shuffle網絡吞吐量的提升。

上面的圖檔對比了單線程對800萬複雜行做shuffle的性能,分别使用的是Kryo和代碼生成,在速度上後者是前者的2倍以上。

Tungsten和未來的工作

在未來的幾個版本中,Tungsten将大幅度提升Spark的核心引擎。它首先将登陸Spark 1.4版本,包括了Dataframe API中聚合操作的記憶體管理,以及定制化序列化器。二進制記憶體管理的擴充和cache-aware資料結構将出現在Spark 1.5的部分項目(基于DataFrame模型)中。當然如果需要的話,這個提升也會應用到Spark RDD API。

對于Spark,Tungsten是一個長期的項目,是以也存在很多的可能性。值得關注的是,我們還将考察LLVM或者OpenCL,讓Spark應用程式可以利用新型CPU所提供的SSE/SIMD指令,以及GPU更好的并行性來提升機器學習和圖的計算。

Spark不變的目标就是提供一個單一的平台,讓使用者可以從中獲得更好的分布式算法來比對任何類型的資料處理任務。其中,性能一直是主要的目标之一,而Tungsten的目标就是讓Spark應用程式達到硬體性能的極限。更多詳情可以持續關注Databricks部落格,以及6月舊金山的Spark Summit。

本文轉自shishanyuan部落格園部落格,原文連結: http://www.cnblogs.com/shishanyuan/p/8453970.html   ,如需轉載請自行聯系原作者