阿里巴巴生态应用在Arm平台性能优化实践

简介: 本次方案的主题是阿里巴巴生态应用在 Arm 平台性能优化实践,分别从背景介绍、编译优化实践、总结和展望三个方面介绍了本主题。1. 背景介绍2. 编译优化实践3. 总结和展望

阿里巴巴生态应用在Arm平台性能优化实践

内容分析:

1. 背景介绍

2. 编译优化实践

3. 总结和展望

 

本次分享主要从以下三个方面展开,首先介绍一下编译优化的背景基础,方便大家理解优化的工作,其次是编译优化实践,是我们具体做的一些编译优化工作相关的技术点,最后是总结与展望,总结优化在业务上带来了哪些效果,以及未来有哪些工作需要继续深入

image.png

 

01.背景介绍


1.1 JavaJIT 编译

image.png

Java 方法在被 JavaC 编译成 Java 自解码后加载到 JVM 中,会存在不同的运行状态。首先它会进入到一个解释执行的状态,这是一个 JVM 最基础的运行状态,效率是比较低的,当执行的次数达到一定阈值,认为这个方法比较热之后,会被 C1 编译器进行编译编译出来可能有 T2或者 T3 两种状态。

这两种编译的结果都同时包含了这个方法本身的一些执行信息,也包含了 Profiled 收集的一些逻辑代码。在 T3 阶段执行到一定程度后,会进入到 C2 编译器的 T4 编译阶段T4 编译会充分利用之前采集Profiled 信息做一些比较极致的性能优化同时我们的期望也是在 T4 阶段能够达到一个比较理想的性能水平因为 T4 水平是 Java 应用对峰值性能下的大部分运行的状态。

image.png

C1 和 C2 编译器编译出来方法最终在 GM 里有 CodeCache 去存放。每个 Java 方法被编译器编译之后,它的基本数据结构都是 MSO 的, CodeCache 是一个 MSO 的基本单位在里面进行排列的。上图黄色的部分保存的是 C1 编译的 T2、T3 的方法,也就是加入了 Profiled 采集的方法,橙色的部分主要保存的是 C2 编译的 Non-profiled 方法,应用运行在峰值性能主要是在橙色的部分,中间灰色的部分是连接 Java 方法之间或者是 Java 方法到 Rentime 方法之间调用的一些过渡代码

 

1.2 阿里巴巴 Java 应用在 Arm 上的表现

image.png

接下来介绍的是阿里的 Java 应用在迁移的过程中遇到的一些问题Arm 平台性价比吸引了应用的开发者从 X86 平台往 Arm 平台进行迁移,但在迁移之后发现 Arm 平台的一些性能和 X86 平台还是有一定差距的。

首先CodeCache 的使用量在 X86 上达到了将近 500 。因为阿里的一些应用本身代码比较复杂,代码规模比较大,所以编译出来的 CodeCache 的规模也会比较大,达到 500 兆。从 X86 迁移到 Arm 的过程中, CodeCache 又进一步膨胀,从 500 多兆膨胀到 600 多兆,增加了将近 20 %,

背后原因有一部分是因为指令本身在架构上存在差异,也有一部分来自于间接跳转其他的一些额外开销。

看到这么大的 CodeCache 于是采集了 Arm 平台的 PMU 数据,发现应用的瓶颈在于 CPU 的前端。 Frontend Idle 达到 50 %以上, Backend Idle 达到了 20 %左右,这给我们后续优化一个方向就是主要针对 Frontend 的一些瓶颈来展开优化

 

02.优化实践

image.png

相关技术主要从以下 4 个方面展开,首先是跳转指令级别的优化,接着是 Block 排布级别的优化,再往上是 Nmethod 数据结构的优化,最后是 CodeCache 分区的优化。

 

2.1 跳转指令级别优化

image.png

从前期采集到 PMU 数据可以看到 Branch Miss 是比较高的,同时也查看了生成的代码,发现里面的跳转指令是比较频繁的,大概平均三到四条指令就会出现一次跳转。编译器角度一个比较直观的优化就是通过 Inline 的方式把被调用的方法直接 Inline 进来减少一些方法调用时候的校准开销。所以接下来的优化也是从这个思路展开的。

上图右侧是一个比较特殊的继承结构图,首先接口 I1 里面生成了一个 Foo 方法,但在整个继承结构里面只有 Class K1 具体实现了这个 Foo 方法,而 Class K2 实际上是调用了 K1 里面的 Foo 。

上图左下角是从 Collar 的视角来看 Interface 调用方法的情况,从 Run 方法里面传过来一个参数 I1 ,它的生命类型是 I1 ,然后通过接口调用的方式去调用 I1 的 Foo 方法,这里 Java GM 在处理的时候通常会生成一个 ITable 表, ITable 表里面填的是每个 I1 的具体实现类型以及 Foo 方法的入口。在通常的情况下它会有多次的跳转来找到这个最终要调用的目标方法,比较特殊的一个情况就是整个继承树里面实际上只有 K1 实现了 Foo 方法。就可以认为这里 I1 的 Foo 实际最终调用的一定是 K1 的 Foo ,更进一步我们可以直接把 K1 点 Foo 的方法调动直接 Inline 进来减少调用开销。

image.png

简单介绍一下编译优化的思路,通过类的继承关系去扫描,如果发现在整个继承树中确实只有这一个实现的话,就会直接把这个方法 Inline 进来,减少跳转本身的开销以及跳转可能带来的 Branch Miss 的惩罚,最终跳转指令和 Branch Miss 都出现了下降

 

2.2 Block 布局优化

image.png

对于一些实在没办法去消除掉的跳转指令,比如条件跳转,可以在 Block 排布的时候把一些更有可能的情况直接放到的路径上,不太可能的路径放在比较冷的路径上Arm的工具 Coresight 提供了一个功能,能够抓到整个程序运行的过程中只跳转指令所出现的一些情况。下面列举了一些抓到的数据,比如跳转的时间戳、跳转的原地址、目标地址以及它们的符号信息基于这些信息我们可以去构建整个程序的冷热代码信息。

image.png

用一个上面的 Case 来代表我们具体遇到的问题。这个方法它传入了一个 Int State ,当 State = 1 的时候,它会执行 Launch Block,如果 State = 2 的话,它会执行 Long_Run Block 。但是我们通过 Coresight 采集到的冷热数据看到在一个条件跳转之后,它紧随其后的是一个冷代码,而热代码反而是离它比较远。就会考虑是不是编译器出错了,或者编译优化的过程出错了,或者采集到的 Profiled 信息不对。最后我们对 Profiled 信息进行操作,结果发现 Launch Block确实一个热代码,而 Long Run 是一个比较冷的代码,这就比较奇怪了 

image.png

于是我们对原程序进行了一个分析,分析发现应用在启动阶段, Run 方法传过来的参数一直是 1 ,但是在 Long Run阶段传过来的参数是2。 Java 方法在 JVM 中运行的状态是有区别的。它在 T2、T3 方法阶段会收集 Profiled ,这是应用启动的初始阶段,等到达一定阈值之后,就会把这些 Profiled 信息用来编译 T4 的方法,最终应用也保持在 T4 方法运行。所以这个问题的原因就呼之欲出了,在应用的启动阶段和应用的 Long Run 阶段,它程序的应用行为是不同的。

我们的 Profiled 信息是在启动阶段收集的,并没有覆盖到 Long Run 阶段的一些行为特征,所以最终导致了冷热代码块不对的情况。我们的优化思路也比较简单,就是尝试提高到 T4 阶段的编译阈值,这样它在启动阶段就能够充分的去采集到 Long Run 阶段的一些 Profiled 信息,最终把这些 Profiled 信息用到 T4 的编译,也能够代表它在 Long Run 阶段的一个程序的行为。当然这个阈值也不能调太高,阈值调的太高的话会导致预约的时间过长,达到峰值状态能够对外提供服务的中值性能的话也更晚,所以这是一个需要去权衡的一个问题。

 

2.3 Nmethod 数据结构优化

image.png

关于 CodeCache 较大,如何将 Codecache里面的水分挤出来的问题,我们做了一些调研。在 Codecache 里面每个 Java 方法编译的最终数据结构是一个 Nmethod 的。在 Nmethod 里面,我们简化成三部分,分别是常量、指令、 Debuginfo 。 Debuginfo 在整个运行过程中并不是很常用,但在当时设计的时候为了方便就放在一起了,同时也最终放到整个 Codecache 里面。

这就导致包含了不常用的数据,占据了我们的空间,导致浪费,所以我们就做了一个优化,就是把这部分 Debuginfo 放到进程 Heap 里面去,从开始踢出去,再用一个指针去指向这个 Debuginfo ,等到需要用它的时候,再去取出来。这样就实现了我们整个 Nmethod 的数据结构的精简,它的指令的密度也实现了提升, Codecache 利用率下降了 20 %左右,这也证明这部分 Debuginfo 之前在整个 Codecache 中占了很大一部分的分量。

 

2.4 Codecache 分区优化

image.png

接下来这部分工作是我们在 Codecache 里面做的一些热代码重排的工作,通过火焰图分析发现整个应用在运行的阶段,它的热点是比较集中的,大概 50 %以上的热点,集中在很有限的方法中。这些方法在整个 Codecache 中的占比大概 5 %左右。同样我们观察到另一个问题,就是它这些方法在整个 Non-Profiled Heap 中是很分散的,这可能带来了一个后果,就是整个 TLB 的切换会比较紧张。

image.png

所以我们就考虑了一个思路,就是把这些热的 Codecache 集中放在一起这样就能减少 TLB 切换的一些压力。在一个应用启动的时候,并没办法去预知将来哪个方法会比较热,我们结合线上现有的工作做了一些整合,通过在线上采集应用热点信息,生成一个热代码的列表,在新的服务启动的时候,就可以把列表使能针对这些热代码集中分配在一块内存区域,这样最后能够降低 TLB 压力。

 

03.总结展望

image.png

通过从指令到 Block 的排布,再到 Nmethod 的数据结构以及 Codecache 代码布局这 4 个层面的优化,极大的缓解了 Arm 前端的压力,最终实现了 CPU 的提升。同时基于在 Arm 上性能的提升,我们的应用对于这个平台的信息也得到极大提升,在某次大促中他们新增了几万盒的一个部署规模。

对于未来展望,我们想继续把以上提到的一些优化再进行细节进一步的提升,另一部分就是希望和 Arm 来共同探索一些底层加工的角度对于前端压力的优化。

相关文章
|
8月前
|
数据采集 监控 安全
精简高效与安全兼备:ARM32与MCU32平台上的信息协议设计新思路
精简高效与安全兼备:ARM32与MCU32平台上的信息协议设计新思路
280 1
|
8月前
|
传感器 数据采集 存储
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用(一)
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用
253 0
|
10天前
|
机器学习/深度学习 边缘计算 PyTorch
PyTorch团队为TorchAO引入1-8比特量化,提升ARM平台性能
PyTorch团队推出创新技术,在其低精度计算库TorchAO中引入低位运算符支持,实现1至8位精度的嵌入层权重量化及8位动态量化激活的线性运算符。该技术通过模块化设计和高效硬件利用,优化了资源受限环境下的深度学习计算,提升了计算效率并降低了资源消耗。新内核与PyTorch生态系统无缝集成,支持即时执行、编译优化及边缘计算,为开发者提供全方位性能优势。测试结果显示,多层次量化策略显著提升了计算效率,保持了模型精度。这一突破为深度学习框架优化开辟了多个研究方向,推动了人工智能在边缘计算等领域的广泛应用。
49 11
PyTorch团队为TorchAO引入1-8比特量化,提升ARM平台性能
|
8月前
|
传感器 Linux 数据处理
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用(二)
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用
136 1
|
3月前
|
数据处理
基于ARM的嵌入式原理与应用:ALU的功能与特点
基于ARM的嵌入式原理与应用:ALU的功能与特点
273 0
|
7月前
|
传感器 物联网 数据中心
探索ARM架构及其核心系列应用和优势
ARM架构因其高效、低功耗和灵活的设计,已成为现代电子设备的核心处理器选择。Cortex-A、Cortex-R和Cortex-M系列分别针对高性能计算、实时系统和低功耗嵌入式应用,满足了不同领域的需求。无论是智能手机、嵌入式控制系统,还是物联网设备,ARM架构都以其卓越的性能和灵活性在全球市场中占据了重要地位。
315 1
|
8月前
|
缓存 Linux
ARM平台内存和cache对xenomai实时性的影响
ARM平台内存和cache对xenomai实时性的影响
165 0
ARM平台内存和cache对xenomai实时性的影响
|
8月前
|
传感器 存储 编解码
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用(三)
ARM Linux摄像头传感器数据处理全景视野:从板端编码视频到高级应用
209 2
|
8月前
|
安全 Linux 数据安全/隐私保护
【SPI协议】了解ARM平台上的SPI的基本应用
【SPI协议】了解ARM平台上的SPI的基本应用
704 0