給 Arm 生態添把火,騰訊 Kona JDK Arm 架構優化實踐

騰源會發表於2021-08-17

 

前言

Arm 架構以其兼具效能與功耗的特點,在智慧終端以及嵌入式領域得到了廣泛的使用,不斷擴大其影響力。而在 PC 端以及資料中心,之前往往是 x86 架構在其中發揮著主要的作用。最近,隨著人工智慧、雲端計算等技術的興起,5G 網路的不斷成熟,萬物互聯的時代是的應用的需求越來越多樣化,使得對於晶片架構的需求也越來越多樣化。

Arm 架構在提供可靠效能的基礎上,低功耗、低開銷的特點使得它被越來越廣泛的應用到資料中心和雲端計算中,成為其中必不可缺少的重要組成部分。亞馬遜投入大量精力自研 Arm 伺服器,並應用到 AWS 服務中,最多實現了成本 45%的降低;阿里巴巴也在雲服務中大量採用 Arm 伺服器,並積極參與 Linaro,Adoptium 等組織,不斷推動 Arm 架構的發展。

最近幾年,騰訊對於 Arm 架構的需求也不斷增加,各個產品線也不斷引入 Arm 伺服器,對於 Arm 架構軟體的需求也在不斷增長。KonaJDK 團隊在騰訊公司內部提供高效能、高穩定性的商用 JDK 版本,堅定地將 Arm 架構作為 KonaJDK 重點支援的架構之一,不斷擴充套件 JDK 在 Arm 架構的功能,並不斷提高 Arm 架構中 JDK 的效能。 

隨著 Arm 架構在終端和雲端計算場景的廣泛應用,JDK 需要做好對於 Arm 架構的支援工作,才能更好地得到發展。目前在 JDK 社群,Arm 架構屬於第一梯隊支援架構。而對於 Arm 架構而言,Java 語言“一次編譯,到處執行” 的特性適合業務應用無縫推廣到 Arm 平臺,而 JDK 則是 Java 應用執行的必要條件。JDK 對於 Arm 架構的支援,也是 Arm 生態推廣的有力支撐。在這個過程中,KonaJDK 團隊希望和 Arm 緊密合作,共同發展。

 

騰訊和 Arm 在 JDK 方面的合作交流

目前騰訊和 Arm 在 JDK 方面已經有了深入的交流和合作。雙方針對 JDK 在 Arm 架構常見的效能問題,對於 Arm 架構新特性的支援情況等方面進行了廣泛和深入的討論,通過效能測試、資料交流、技術研討等形式不斷推動 JDK 在 Arm 架構的發展。

 

KonaJDK 團隊 Arm 平臺優化技術介紹 

目前在 Arm 架構,KonaJDK 平臺已經發布了 JDK8 和 JDK11 兩個版本,在 2021 晚些時候還會發布最新的 JDK17 版本。Kona JDK 團隊從功能、效能多方面出發,在 Arm 架構支撐 KonaJDK 的通用特性,並針對架構特徵進行優化,保證 Java 應用向 Arm 平臺遷移的一致性,為 Arm 架構推廣做好準備。

ZGC

GC 使得程式不再需要手動控制記憶體的釋放,有效的降低了記憶體管理相關錯誤產生的可能性。但是,對於 GC 演算法而言,如何準確高效的進行記憶體清理是一個複雜的過程。隨著業務需求的不斷髮展,GC 演算法也在不停地迭代,只有針對不同的業務目標,選擇最合適的 GC 演算法,才能夠更好的幫助業務實現其目標。近幾年,隨著伺服器硬體效能越來越強勁,其軟體應用往往也需要更大的堆,從 10G 到 100G,甚至 TB 級別。在這種環境下,傳統的 CMS、G1 等 GC 演算法,其停頓時間往往隨著堆大小的增長而增加,對於超大堆在觸發 Full GC 的時候,甚至可能產生分鐘級別的停頓,這樣對於延遲敏感的應用來說,GC 停頓已經成為阻礙 其廣泛應用的一大頑疾,需要更適合的 GC 演算法以滿足這些業務的需求。

ZGC 是由 JEP333 引入 JDK,希望徹底解決 GC 停頓帶來的延遲問題,其設計目標為:每次 GC 停頓時間控制在 10ms 以下;相對於 G1 GC,吞吐率下降不超過 15%;支援大堆和特大堆,並且停頓時間不隨著堆大小的增長而增長。ZGC 從 JDK11 開始推出實驗性版本,並隨著 JDK 新版本釋出不斷補充完善,最終在 JDK15 中成為正式版本,保證了 Java 停頓時間不會隨著堆大小和業務規模的增加而增長。為對 GC 停頓要求高的業務提供了一種更好的選擇。

 

給 Arm 生態添把火,騰訊 Kona JDK Arm 架構優化實踐

圖 1 ZGC 效能(出自 The Design of ZGC,Per Lidén)

 

KonaJDK 團隊為了滿足業務的需求,在 Tencent Kona JDK11 版本中,完善了 ZGC 功能的補全,並進行了長期的驗證落地,使得對 GC 停頓敏感的業務也能夠在 JDK11 版本中滿足對於低 GC 延遲的需求。JDK11 在 2018 年下半年釋出,屬於 Long-Term Support 版本,而後續 LTS 版本為 JDK17,預計將於 2021 年 9 月釋出,中間其他版本屬於過渡開發版本,沒有持續的更新和修復。因此,KonaJDK 團隊選擇在 JDK11 完善 ZGC 的功能,滿足業務的需要,即使後續 JDK17 釋出之後,業務版本更新也需要一個過程,在這期間,仍然需要 JDK11 的支援。 

對於 Arm 架構而言,在 JDK11 支援 ZGC 相對於 x86 架構是一個更大的挑戰。x86 架構從 JDK11 開始 ZGC 就作為 Experimental 特性開始釋出,但是在 Arm 架構,從 JDK13 才有對於 ZGC 的支援。KonaJDK 團隊進行了大量的工作完成了 Arm 架構在 JDK11 中對於 ZGC 的支援: 

  • 需要選擇 JDK 在 Arm 架構中合適的提交移植到 JDK11 版本

  • 從 JDK11 到 JDK13,ZGC 程式碼以及 Hotspot 程式碼經過了多次重構,在程式碼移植過程中需要分析程式碼重構的功能以及影響,或者移植相關重構程式碼,或者根據 JDK11 對相關程式碼進行適配修改

  • 根據 Arm 架構的特徵,適配團隊對於 ZGC 的優化、功能增強以及 Bug 修復。

  • Arm 屬於 RISC 架構,而且使用弱有序記憶體模型,因此在適配相關彙編程式碼(特別是 ZGC 使用的 barrier)時,需要仔細斟酌指令的選擇,在保證正確性的基礎上儘可能的降低開銷,提高效率

  • 在 Arm 平臺進行充分、全面的測試,保證相關程式碼的健壯性

 

KonaJDK 團隊在 Arm 結構支援 ZGC 的過程中,遇到的最大困難在於如何正確新增 barrier 指令保證正確性。由於 Arm 使用弱有序記憶體模型,在 x86 平臺能夠正確執行的程式碼在 Arm 架構下可能由於缺少必要的 barrier 導致產生隨機錯誤。KonaJDK 團隊在初步完成 ZGC 支援程式碼之後,進行 ZGC 壓力測試過程中,發現存在執行若干次 GC 之後,存在 JDK 隨機崩潰的現象,發生機率幾千分之一。通過對錯誤現場的分析,大概率懷疑是缺少必要的 barrier 所致。嘗試通過對社群程式碼以及 ZGC 邏輯對問題進行分析,在這個過程中,JDK13 和 JDK11 程式碼結構的不同進一步加大了分析的難度,最終 KonaJDK 團隊完成該問題的修復,ZGC 程式碼在 Arm 架構連續執行數百萬次無問題。

和其他 GC 演算法一樣,ZGC 也有其適用的業務場景。ZGC 演算法最大的優勢是能夠將停頓時間控制在 10ms 以下,特別適合對於停頓時間敏感的業務。但是為了實現如此短的停頓時間,ZGC 的代價是一部分效能損失和記憶體消耗。ZGC 通過將若干任務進行併發化改造,使得若干之前必須在停頓時完成的工作,可以和應用程式碼併發執行,有效的降低了必須的停頓時間。但是這種併發執行,以及其引入的各類 Barrier,也會導致一定程度的應用吞吐率下降。通過整個 OpenJDK 社群的持續投入,當前 ZGC 在效能損失場景中的效能下降已經控制在很小的範圍內。對於效能來說,如充足的記憶體下即大堆場景,ZGC 在各類 Benchmark 中能夠超過 G1 大約 5% 到 20%,而在小堆情況下,則要低於 G1 大約 10%。

因此,不同的業務需要根據實際的情況,選擇更為合適的 GC 演算法,來保證吞吐率和停頓時間都能夠滿足業務的需求。目前來看,如果業務應用使用了超大堆(幾十 G 甚至上百 G)為了避免傳統 G1 等 GC 演算法 Full GC 時帶來的幾十秒甚至分鐘級別的停頓,建議使用 ZGC。另外如果業務對於停頓時間的有著嚴格的時限要求,那麼也建議使用 ZGC。

KonaFiber

應用在需要併發執行多項任務的時候,會建立多個執行緒,每個執行緒負責一項任務,從而實現任務的併發執行。但是,隨著業務規模的不斷增大,如果仍然為每一個任務建立一個執行緒,由於執行緒本身記憶體消耗較大,會導致佔用大量的記憶體。另外,執行緒切換需要核心完成,大量的執行緒存在時,其頻繁的切換開銷也會影響併發執行的效率。協程就是為了解決這種情況而誕生的。協程是一種輕量級的執行緒,兼顧開發效率和執行效率。協程的切換在使用者態完成,比執行緒切換開銷小很多,同時對於記憶體的需求更低,相對的需要應用程式碼編寫時關注部分協程切換的工作。協程相對於執行緒,在高併發場景能夠取得更好的效能,應用越來越廣泛。OpenJDK 也啟動了 Java 協程原生支援專案:Project Loom,開發時間超過 3.5 年,並在不斷髮展完善,即將成為 Experimental 特性。

KonaFiber 是 KonaJDK 團隊實現的協程方案,它在相容 OpenJDK 社群 Loom API 的同時,提供了更好的切換效能,不過需要部分額外的記憶體開銷。KonaFiber 根據業務的需要,目前在 JDK8 和 JDK11 實現,和社群相容的 API 使它成為可以和社群方案一起長期演進的協程方案。目前 KonaFiber 已經完成對於 Arm 架構的支援,能夠滿足 Arm 架構應用對於協程的需求。

給 Arm 生態添把火,騰訊 Kona JDK Arm 架構優化實踐

圖 2 KonaJDK 和 Loom 對比

為了滿足業務的需求,提供更好的協程切換效能,KonaFiber 採用了基於 JKU 的 StackFul 有棧方案,為每一個協程建立獨立的堆疊。當進行協程切換的時候,JDK 在對於協程 Pin 狀態檢測以及上下文儲存之外,只需要修改 Frame Pointer 和 Stack Pointer 的值就可以完成協程的切換工作,邏輯簡單且效能開銷很小。不過相對於社群的方案,KonaFiber 的 StackFul 方案對於記憶體的使用要多一些,更適用於對於記憶體消耗不太敏感,但是對於效能更敏感的業務場景。效能資料如圖 2 所示,左圖表示在不同協程數量情況下,每秒內協程切換次數對比;右圖對記憶體消耗進行了對比。 

給 Arm 生態添把火,騰訊 Kona JDK Arm 架構優化實踐

圖 3 KonaFiber 效能對比

KonaFiber 的實現注重優化以及程式碼重構,通過多種方式不斷進行優化:

  • 協程輕量化,不斷優化降低協程的資源消耗

  • 按需建立,根據業務的需要建立協程,降低記憶體使用

  • GC 優化,優化實現,降低協程對 GC 引入的開銷

  • 穩定性修復,通過廣泛的測試以及業務適配,提高健壯性

 

相對於 OpenJDK 社群的協程方案 Loom,KonaFiber 提供了更高更穩定的排程效能。圖 3 對比了 KonaFiber 和 Loom 在不同協程數量情況下的每秒排程次數。

 

給 Arm 生態添把火,騰訊 Kona JDK Arm 架構優化實踐

圖 4 排程效能對比

 

目前 KonaFiber 在 KonaJDK8 中已經開源,後續也會在 KonaJDK11 中開源,KonaJDK 也會持續跟進 Loom 社群並不斷完善 KonaFiber 的實現。

OWST 優化

GC 執行過程中,存在若干 GC 執行緒並行處理各種任務,但是不同任務的處理時間不等,使得各個 GC 執行緒之間負載分配並不平衡。JDK 中通過如下的方式來平衡各個 GC 執行緒之間的負載,降低 GC 的停頓時間:當一個 GC 執行緒執行完成它被分配的任務之後,會檢視其它 GC 執行緒的任務佇列,如果存在這個執行緒可以執行的任務,那麼它會將該任務“偷取”過來並執行。該過程持續迴圈直到 GC 結束。該方案實現了負載的自動平衡,但是執行過程中,由於可能多個 GC 執行緒同時“偷取”任務,線上程數量較多時,鎖的競爭會比較激烈,同時搶鎖過程中,各個 GC 執行緒的自旋等待也會導致一定的效能開銷,使得該演算法實際表現差強人意。

為了優化這個過程,Google 在 ISMM 2016 發表了的論文提出了一種新的負載均衡演算法:Optimized Work-Stealing Threads(OWST)。該演算法的基本思想是:當存在多個 GC 執行緒需要“偷取”任務時,最終只有一個執行緒執行“偷取”操作,其它執行緒進入等待狀態。執行“偷取”操作的執行緒檢查各個 GC 執行緒的任務佇列,根據任務個數喚醒執行緒,並執行任務。演算法有效的降低了各個 GC 執行緒之間對於鎖的競爭,提高了整個負載均衡的效率。

OpenJDK 社群首先在 Shenandoah GC 上實現了 OWST 演算法,在 JDK12 版本中合入主幹分支併成為預設的 Parallel Terminator。為了更好地支援 LTS 版本,KonaJDK 團隊將 OWST 演算法相關程式碼移植到 JDK8 和 JDK11,並完成相關程式碼適配和測試工作,經過業務端驗證,為 JDK8 和 JDK11 新增了商用的 OWST 演算法支援,有效降低了 GC 並行任務的執行時間,降低了 GC 的停頓時間。

通過對 SPECjbb2015 的效能進行測試,使用 ParallelGC 時 OWST 在對於 max-jOPS 基本沒有影響的前提下,能夠提升大約 8%的 critical-jOPS 評分。另外對於騰訊內部大資料相關的 Map/Reduce 以及 Spark SQL 任務進行測試,執行效能也有 10+%的提升。

業務應用

目前在 Arm 架構,ZGC 已經在騰訊的大規模生產中得到了實踐應用。

ZGC 將停頓時間控制在 10ms 以下的特性,使得它特別適合停頓時間敏感的業務。騰訊的 WAF 團隊使用 Java 語言來快速實現產品功能迭代及上線。該團隊有一個旁路安全服務,是一個基於 Netty 框架的 Http 服務。它對於端到端請求的時延要求特別嚴格,需要達到 99.99% 請求時延小於 80ms 的 SLA 目標。傳統的 GC 演算法,難以達到如此高的標準,較長的停頓時間對於該服務有一定的負面影響,需要尋找一種更低停頓時間的 GC 演算法。WAF 團隊之前採用了 G1 GC 演算法,花費了大量的時間和精力對 G1 GC 進行選項調優以及程式碼層面的修改,但由於 G1GC 本身的不足,仍然存在請求抖動延遲,無法達到既定的 SLA 目標。後續在 KonaJDK 團隊的配合下,通過切換 ZGC 演算法,實現了該業務的 P9999 請求延遲穩定小於 80ms,為使用者提供了更快速、穩定的服務。

後續計劃 

目前 KonaJDK 團隊在 Arm 架構,主要在 JDK8 和 JDK11 版本進行優化和支撐,後續也會支撐 JDK17 等版本。KonaJDK 團隊會持續對 JDK 基礎類庫、執行時、記憶體管理、執行引擎等等各個模組進行分析和測試,不斷擴充套件 JDK 的功能,提升效能。

KonaJDK 團隊會始終將 Arm 架構作為重點支撐平臺,不斷加大投入,推動並完善 JDK 對於 Arm 架構的支援,滿足對於 Arm 架構不斷增長的需求。

 

歡迎關注「騰源會」微信公眾號,這裡是全球開源內容資訊的聚集地,包括但不限於全球開源資訊,開源專案及技術文章,開源活動報導及開源人物採訪。

 

TencentKona 11專案連結:

https://github.com/Tencent/TencentKona-11

 

相關文章