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

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

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

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

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

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

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

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