接着上篇文章
安卓性能绝版:Android卡顿掉帧问题分析之实战篇1-流程执行异常
安卓性能绝版2:Android卡顿掉帧问题分析之实战篇2-系统负载异常
,老东家的同事分享一些异常的性能问题案例,今天继续分析性能问题之编译引起的性能问题。
(这些文章已经找不到简书原版了,因为未知原因删除了,大家记得收藏)
3 编译引起卡顿性能问题
编译引起的性能问题主要分为两类:
一类是代码的解释执行耗时,运行时 VerifyClass 执行耗时等引发卡顿;
二类是编译本身引起的卡顿问题,比如 dex2oat 编译抢占 CPU 算力资源,进程中的 Jit 编译线程抢占 CPU 算力资源。
3.1 代码解释执行耗时
一、理论分析
Art 虚拟机有两种代码执行模式:quick code 模式和 Interpreter 模式。对于 dex 字节码文件采用的是 Interpreter 解释执行的模式,每次执行代码,虚拟机需要将代码转换成机器指令集,然后交给 CPU 去执行,所以执行效率比较低(早期 Android 系统被诟病性能差的原因之一)。而对于经过编译(AOT 的 dex2oat 编译或 JIT 运行时及时编译,都需要消耗一定的磁盘空间存储编译出来的机器码文件)后生成的 ELF 格式机器码文件(如 .oat 格式文件、.art 格式文件),采用 quick code 模式,直接执行 arm 汇编指令,执行效率较高。为了做到性能、磁盘空间占用以及应用安装速度之间的最佳平衡,从 Android 7.0 开始,采用 AOT + JIT + 解释执行的混合模式,其特点如下:
应用在安装的时候 dex中大部分函数代码不会被编译。App运行时,dex文件先通过解析器被直接执行,热点函数会被识别并被JIT编译后存储在jit code cache中并生成profile文件以记录热点函数的信息。手机进入 IDLE(空闲)或者Charging(充电)状态的时候,系统会扫描App目录下的profile文件并执行AOT过程进行编译。
所以 ART 虚拟机在执行一个函数过程中会在 Interpreter 解释器模式和 quick code 模式切换,具体切换规则如下图所示:
!JIT工作流程.png
从上图可以看出,有些时候应用部分代码还是按照 Interpreter 模式执行,运行效率较低,从而产生一些性能问题。
二、典型案例分析
问题描述:淘宝等三方应用冷启动速度慢于同平台竞品机型。
问题分析:结合 Systrace 工具分析问题原因如下:
!with_jit.jpg
!without_jit.jpg
从与竞品机器的对比 Systrace 分析可以看到:同一个应用,且在两台手机 CPU 型号和运行主频一致的情况下,竞品机器上应用冷启动期间,UI 线程的 Running 时长更短,且 Jit 工作线程上的热点代码编译任务明显少很多。从而可以推断出,问题原因在于竞品机器上的应用代码大部分是以机器码直接执行,而我们是以字节码解释执行,所以效率更低,运行时间更长。为什么会出现这个差异呢?后来对比日志分析发现,原因是竞品机上应用第一次安装打开后一段时间后,系统会触发一次 speed-profile 模式的 dex2oat 编译,会根据应用运行期间 JIT 搜集到的热点函数信息保存后生成的 Profile 描述文件进行编译,从而将应用启动期间的大部分代码函数编译成了机器码 .oat 文件,后续再启动应用时即可直接以 quick code 模式执行机器码,所以执行效率更高。
三、优化思路
针对代码解释执行造成的性能问题,大致优化思路是尽量让应用代码以机器码形式运行,部分有效的手段有:
部分手机厂商,在手机预置的应用商店中下载的三方应用,除了 APK文件外,还会包含保存有应用热点函数信息的Profile描述文件,这样在应用dex2oat安装时,选择speed-profile模式,就会根据Profile描述文件,将应用的大部分热点函数直接编译成机器码,从而极大的提升应用的性能表现。在手机息屏充电,进入 Idle状态后,通过JobScheduler机制,启动后台服务,扫描App目录下的profile文件并执行AOT过程进行编译,以达到手机越用越快的效果。
3.2 编译本身耗时
一、理论分析
在 dex2oat 进程编译应用的过程中,系统会启动很多线程,消耗大量的 CPU 算力资源。可能会导致前台应用由于抢不到 CPU 算力资源,其 UI 线程长时间处于 Runnable 状态而卡顿。另外应用进程内部,Jit 工作线程动态编译时持续运行,如果负载较重,持续跑到 CPU 大核上,也会造成 CPU 算力资源抢占。
二、典型案例分析
问题描述:抖音应用界面严重卡顿。
问题分析:结合 Systrace 工具分析问题原因如下:
!dex2oat block.jpg
从上图可以看出:抖音应用界面卡顿的原因是因为其 UI 线程抢不到 CPU 算力资源而长时间处于 Runnbale 状态,而导致这个问题的很大一部分原因就是此时后台 dex2oat 进程创建多个线程持续执行应用编译动作抢占 CPU 算力资源。
三、优化思路
针对编译本身耗时引起的性能问题,部分有效的手段有:
通过 cpuset配置,限制dex2oat进程和Jit工作线程对CPU的使用,使其不抢占CPU大核心算力资源,并设置参数限制其创建的线程数。dex2oat编译应用的动作尽量放在设备息屏,设备处于Idle状态下进行,以免影响前台用户的操作;限制三方应用在后台触发执行 dex2oat编译动作(从Android 10开始,谷歌官方通过Selinux权限的管控,禁止了三方应用触发dex2oat的权限);
学习fw课程和性能相关知识有啥疑问,请记得联系马哥本人或者在马哥vip群中进行讨论
更多vip干货独享知识,及面试定制指导上岸fw工程师服务,课程优惠购买成为vip学员进入vip群,积极讨论各种行业难点痛点疑难问题,答疑服务等。
请联系马哥微信:

夜雨聆风