作者:安謀科技首席軟件工程師 Yanqin Wei
Java 是互聯網領域廣泛使用的編程語言。Java 應用的一些特性使其性能表現與提前編譯的原生應用(例如 C 程序)大相徑庭。由于 Java 字節碼無法直接在 CPU 上執行,因此通常運行時在 Java 虛擬機 (JVM) 內執行。JVM 必須先通過解釋器或即時 (JIT) 編譯器將字節碼轉換為機器碼,而運行時生成的機器碼對 Java 應用的效率和性能至關重要。
在電子商務等一些互聯網領域,程序需要處理多樣化的用戶輸入,同時提供豐富的功能。例如,電子商務應用通常集成產品瀏覽、搜索和篩選,購物車、營銷活動、訂單管理和支付系統等功能。
每項功能都需要大量的運行時代碼、數據及第三方庫。因此,基于 Java 的電子商務應用在運行時可能會被編譯為龐大的機器碼,這些機器碼存儲在“代碼緩存”中,并被反復執行。
大代碼量對性能的影響
在 Hotspot JVM 中,代碼緩存是一種分配在連續內存區域中類似于堆的結構。它會按代碼類型劃分為多個段,用戶可根據應用需求配置各段的大小。這種設計能減少不同生命周期代碼混合造成的內存碎片。這些段包括:
非方法段:包含編譯器緩沖區、字節碼解釋器等非方法代碼。這類代碼會永久駐留在代碼緩存中。
Profiled nmethod:包含經過輕度優化和性能分析的方法,其生命周期較短。
Non-profiled nmethod:包含經過完全優化、無需性能分析的方法,其生命周期可能較長。
C2 編譯器將原生代碼存儲在 non-profiled nmethod 中。該段既包含頻繁執行的熱點代碼,也包含啟動期間多次執行、但后續極少調用的代碼。
現代 CPU 采用深度管線設計,并包含多個執行單元。Arm Neoverse CPU 的前端從內存中獲取指令,并將其解碼為稱為“微操作”的底層硬件操作;后端則調度這些操作,并在 Neoverse CPU 上以亂序方式執行。代碼量過大會影響 CPU 前端性能,具體表現包括指令獲取延遲、ITLB 重新填充、指令管線排空,以及分支目標緩沖區條目重新填充等。

大代碼實驗
我們開展了一項 10 倍代碼擴容實驗,以模擬大代碼緩存場景。通過將 nmethod 所需內存人為地擴容 10 倍,我們創建了一個已用代碼緩存巨大的應用,并使用 DaCapo Java 基準測試來衡量其對性能的影響。在 Neoverse N2 平臺上運行該實驗時,我們發現吞吐量(約 4-6%)和長尾延遲(約 1-3%)均有所下降。下圖展示了使用 DaCapo 基準測試中 Spring 測試用例收集的 PMU 統計數據,其中 non-profiled nmethod 大小被放大了 10 倍。

該實驗無法完全模擬大代碼 Java 應用,因為它僅導致編譯器生成的代碼在內存地址上分散分布。因此,其性能數據不能完全反映真實場景,但 PMU 統計數據仍能揭示其對前端性能的影響。
此實驗不會改變執行的指令或使用的數據,僅會擴大代碼的空間分布,最終導致 CPU 前端資源成為瓶頸。
性能優化
這一性能瓶頸與前端資源大小密切相關,包括緩存大小、BTB 大小和 iTLB 大小。不同的 Neoverse CPU 擁有不同的資源規模。無論資源規模如何,我們都可以通過軟件或配置優化來減輕這一瓶頸的影響。
將數據移出代碼緩存
在代碼緩存中,每個編譯后的方法都包含代碼和數據。這些數據包括方法頭、重定位數據、普通對象指針、JMCI 數據、反優化數據、作用域元數據等。通過盡可能從代碼緩存中移除數據,可有效減小其占用空間。這種優化能提高代碼密度,使得調用這些函數時能更好地利用 CPU 的 L1/L2 緩存、iTLB 和 BTB 資源。
我們嘗試將多個補丁反向移植到 OpenJDK 21 中,以在代碼擴容實驗中衡量它們對性能和 PMU 的影響。這些補丁旨在減小 nmethod 頭的大小,并將大部分不可變數據和可變數據從代碼緩存中移出。
8329433:減小 nmethod 頭大小
https://github.com/openjdk/jdk/commit/b704e91241b0f84d866f50a8f2c6af240087cb29
8331087:將不可變 nmethod 數據從代碼緩存中移出
https://github.com/openjdk/jdk/commit/bdcc2400db63e604d76f9b5bd3c876271743f69f
8343789:將可變 nmethod 數據從代碼緩存中移出
https://github.com/openjdk/jdk/commit/83de34041eacdf987988364487712c79bbb4c235
在我們的實驗中,non-profiled nmethod 大小減小了 39%,從 229MB 降至 149MB。在 DaCapo 基準測試結果中,隨著前端性能指標的優化,吞吐量和長尾延遲均有所改善。從收集的 PMU 數據可以看出,緩存填充、iTLB 重新填充和分支未命中的 MPKI 均有所下降。
其原因在于,代碼空間局部性的提升提高了前端資源的使用效率,從而加快了指令的獲取和解碼操作。

為代碼緩存啟用透明大頁
在大代碼量的 Java 應用中,執行代碼的地址范圍較廣,這意味著 CPU 需要更多的 MMU 和 TLB 資源來存儲虛擬地址到物理地址的映射,進而影響此類應用中的 iTLB 填充性能。對代碼緩存區域應用透明大頁 (THP) 可以增大頁表項大小,減少所需頁表的總數,從而減少 iTLB 資源占用。
在 OpenJDK 中,若 Linux 操作系統支持,啟用 -XX:+UseTransparentHugePages 選項會為代碼緩存堆應用 2MB 的大頁。使用這種配置時,可以觀察到性能和 iTLB 填充 PMU 指標均有改善。

代碼緩存中的熱點方法段
在穩定工作負載中,熱點代碼的總大小通常較小。由于分層編譯的機制,熱點代碼通常存在于 non-profiled nmethod 中。第 4 層 (T4) 方法是在被多次使用后,由 C2 編譯器按照其活躍使用檢測的順序進行 JIT 編譯,因此熱點代碼和冷代碼往往是交織的。
為了提升 CPU 前端性能,在代碼緩存中設置熱點方法段可以增強頻繁執行代碼的空間局部性。將熱點方法集中存放能夠提高指令獲取和解碼的效率。
要確定哪些方法應放置在該熱點區域,需要使用分析工具收集性能剖析數據。一種方案是利用 Java Flight Recorder (JFR) 在運行時動態調整代碼布局,但這種方案較為復雜,且方法重定位會帶來額外的性能開銷。
或者,可以提前預定義熱點方法,其步驟如下:
1.在首次運行時,使用 async-profiler 等工具找出第 4 層熱點方法。
2.通過自定義腳本解析 JVM 編譯日志,獲取這些方法的大小。
3.生成熱點方法列表,使其大小符合代碼緩存中預定義的熱點段大小。
4.創建指令文件,指導 JIT 編譯器在下次運行時對熱點方法進行優化放置,避免運行時重定位的開銷。
如前所述,將 nmethod 拆分為頻繁訪問部分和非頻繁訪問部分,并分別分配內存。新增的熱點代碼段可放置在非 nmethod 段與 non-profiled nmethod 段之間,以保持熱點代碼空間局部性。

熱點段存在一個副作用:它會將原本相鄰的一些 non-profiled nmethod 移至不同的段中。在某些情況下,內存地址相鄰的方法也會被連續調用,而這種重定位會導致這些連續調用的方法被放置在不同的內存頁表中,而非共享同一頁表。這會增加指令 TLB 的負擔。如前文所述,為代碼緩存啟用透明大頁 (THP) 可通過減少所需頁表項的數量來緩解此問題。因此,在使用熱點 nmethod 段時,應啟用該功能。
CPU 系統寄存器配置
Arm Neoverse 核心提供了若干硬件寄存器,用于調控 CPU 緩存行為。在 Neoverse N2 中,IMP_CPUECTLR_EL1 寄存器包含多個字段,這些字段會影響指令獲取過程中 L2 緩存的使用方式。
CMC_MIN_WAYS 能夠限制 CMC 預取可使用的 L2 緩存路數。其默認值為 2,即 CMC 必須為 L2 緩存中的數據保留至少 2 路。在前端瓶頸場景中,將該值設為 0 可預留更多 L2 緩存用于指令獲取。
L2_INST_PART 可將部分 L2 緩存專門預留用于存儲指令,默認處于禁用狀態。啟用這一專用空間能夠提高指令獲取時的緩存命中率。
在代碼膨脹實驗中,將 CMC_MIN_WAYS 設為 0 且 L2_INST_PART 設為 2 后,吞吐量和延遲均得到顯著改善。

總 結
針對 Arm Neoverse CPU 上大代碼量 Java 應用的性能測試與調優表明,過大的代碼緩存會顯著影響 CPU 前端效率。代碼緩存膨脹實驗顯示,由于 CPU 緩存、TLB 和分支預測單元等前端資源的壓力陡增,性能出現了明顯下降。
為解決這些瓶頸,我們建議多項軟件優化方案,包括:
將數據移出代碼緩存,減小已編譯方法的內存占用。
為 JVM 代碼緩存堆啟用 THP。
引入專用熱點方法段,提升空間局部性。
配置 CPU 寄存器,為指令獲取預留更多緩存空間。
這些方法能夠改善大代碼量 Java 應用的性能。在 DaCapo 基準測試中,部分方法在代碼膨脹 10 倍的情況下,仍實現了吞吐量和延遲的改善。這些優化與配置可顯著緩解由大代碼緩存導致的前端瓶頸,進而提升 Neoverse CPU 上 Java 工作負載的執行效率。
-
ARM
+關注
關注
135文章
9511瀏覽量
389239 -
JAVA
+關注
關注
20文章
2997瀏覽量
115799 -
代碼
+關注
關注
30文章
4947瀏覽量
73291 -
Neoverse
+關注
關注
0文章
15瀏覽量
4938
原文標題:優化大代碼量 Java 應用在 Arm Neoverse 平臺上的代碼緩存性能
文章出處:【微信號:Arm社區,微信公眾號:Arm社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
ARM Neoverse系列服務器CPU研究分析
Arm Neoverse NVIDIA Grace CPU 超級芯片:為人工智能的未來設定步伐
ARM Neoverse IP的AWS實例上etcd分布式鍵對值存儲性能提升
Arm Neoverse V1的AWS Graviton3在深度學習推理工作負載方面的作用
ARM Neoverse N1 Core性能分析方法
Arm Neoverse V1 PMU指南
Arm Neoverse N1軟件優化指南
Arm Neoverse? N1 PMU指南
互聯網巨頭紛紛啟用Arm CPU架構,Arm最新Neoverse V1和N2平臺加速云服務器芯片自研
智原與Arm合作提供基于Arm Neoverse CSS的設計服務
Arm 更新 Neoverse 產品路線圖,實現基于 Arm 平臺的人工智能基礎設施
Arm發布Neoverse V3和N3 CPU內核
Arm Neoverse CSS V3 助力云計算實現 TCO 優化的機密計算
Arm新Arm Neoverse計算子系統(CSS):Arm Neoverse CSS V3和Arm Neoverse CSS N3
如何在基于Arm Neoverse平臺的CPU上構建分布式Kubernetes集群

Arm Neoverse CPU上大代碼量Java應用的性能測試
評論