给Arm生态添把火,腾讯Kona JDK Arm架构优化实践
前言
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方面的合作交流
KonaJDK
目前騰訊和Arm在JDK方面已經有了深入的交流和合作。雙方針對JDK在Arm架構常見的性能問題,對于Arm架構新特性的支持情況等方面進行了廣泛和深入的討論,通過性能測試、數據交流、技術研討等形式不斷推動JDK在Arm架構的發展。
KonaJDK團隊Arm平臺優化技術介紹
KonaJDK
目前在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停頓要求高的業務提供了一種更好的選擇。
圖 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架構應用對于協程的需求。
圖 2 KonaJDK和Loom對比
為了滿足業務的需求,提供更好的協程切換性能,KonaFiber采用了基于JKU的StackFul有棧方案,為每一個協程創建獨立的堆棧。當進行協程切換的時候,JDK在對于協程Pin狀態檢測以及上下文保存之外,只需要修改Frame Pointer和Stack Pointer的值就可以完成協程的切換工作,邏輯簡單且性能開銷很小。不過相對于社區的方案,KonaFiber的StackFul方案對于內存的使用要多一些,更適用于對于內存消耗不太敏感,但是對于性能更敏感的業務場景。性能數據如圖 2所示,左圖表示在不同協程數量情況下,每秒內協程切換次數對比;右圖對內存消耗進行了對比。
圖 3 KonaFiber性能對比
KonaFiber的實現注重優化以及代碼重構,通過多種方式不斷進行優化:
協程輕量化,不斷優化降低協程的資源消耗
按需創建,根據業務的需要創建協程,降低內存使用
GC優化,優化實現,降低協程對GC引入的開銷
穩定性修復,通過廣泛的測試以及業務適配,提高健壯性
相對于OpenJDK社區的協程方案Loom,KonaFiber提供了更高更穩定的調度性能。圖 3對比了KonaFiber和Loom在不同協程數量情況下的每秒調度次數。
圖 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+%的提升。
業務應用
KonaJDK
目前在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
目前KonaJDK團隊在Arm架構,主要在JDK8和JDK11版本進行優化和支撐,后續也會支撐JDK17等版本。KonaJDK團隊會持續對JDK基礎類庫、運行時、內存管理、執行引擎等等各個模塊進行分析和測試,不斷擴展JDK的功能,提升性能。
KonaJDK團隊會始終將Arm架構作為重點支撐平臺,不斷加大投入,推動并完善JDK對于Arm架構的支持,滿足對于Arm架構不斷增長的需求。
往期精選
掃碼關注 | 即刻了解騰訊大數據技術動態
總結
以上是生活随笔為你收集整理的给Arm生态添把火,腾讯Kona JDK Arm架构优化实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 别琢磨了,七夕礼物都给你想好了
- 下一篇: 消息队列背后的设计思想