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

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

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

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

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

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

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

Arm Neoverse CPU上大代碼量Java應(yīng)用的性能測試
評論