一、项目简介:
综述:dex2oat编译等级优化方案
作用:提高应用运行,启动速度,优化性能等作用
主要担任角色:调研原生编译方案,性能数据收集,优化路径切入尝试…等
二、项目背景:
一, 这个项目面向的业务场景是什么?
基于Android平台的编译优化方案。由于车机芯片,硬件方面等原因…应用启动,运行速度包括性能方面并不是很好达到厂商的要求,于是急需扩展Android原有编译优化策略,增加编译等级优化等措施来优化。
二, 需要解决的业务痛点及原因
根据对CPU占用率,前后台进程内存占用等相关性能数据的收集发现:CPU反复在一段时间内飙升,过一段时间又恢复。猜测原因是:飙升时应用进行JIT由于芯片方面并不是很好所以这段时间会出现卡顿,假死等情况;而过一段时间恢复后又因为这些JIT时收集的性能数据达不到要求无法进行安卓的编译优化处理。
三、相关知识:
优化原理
为什么要优化
为什么说经过oat之后的代码比jit的代码执行速率高:这其实类似于学习一门外语的过程~
不同过程也意味着不同的优化等级~(后文会提到)
一,入门初学
jit解释执行,相当于对每个英语单词都要去词典里面查找注释翻译 (这个时候查看英语文档效率最慢,单词语义不认识,语法也不熟悉,语句更不知道什么意思~)
二,认识单词
当某个单词见得次数多了也就记住了不需要查看词典翻译了,但是这个时候只是单词知道什么意思了,语法也不懂,连起来也还是不知道什么意思(编译为机器码的操作,硬生生的将dex字节码翻译成机器码)
三,认识常用短语,语法
当你学会一些语法或者短语时,你会不由自主的用这些东西去套到你的语句里面来更好的理解这句话是什么意思。(这个时候会根据你的程序逻辑生成语法树,逻辑控制流,主要是针对你的应用代码的优化
也就是高级中间表达形式优化操作:比如常量传播,公共表达式提取,方法内联等操作~)
四,学习读音~
你已经学会了看英文文档,当你去外国旅游时想秀一下自己的英语水平时尴尬了,不同国家读音还不一样(虽说英文是国际标准吧)但是还是有人只能听懂自己国家的听不懂英语
还会进行优化 只不过这部分是针对不同硬件平台的优化。也就是低级中间表达形式优化操作根据相关平台的机器码进行再次优化处理~选用效率最好的对应平台的机器码
五,环游世界~
将中间表达形式生成的语法树转换树进行替换成对应平台的机器码---->
到这个阶段才是最终运行效率最高的机器码~
优化单位
可以看到学习外语的基本单位是基于单词,一步一步学习最后才可以做到环游世界。(学习英语的时间越长, 阅读英文文档的速度也会得到提升)
程序编译优化也是有基本单位的:函数;一步一步的优化操作对应于优化等级。(编译优化的时间越长,效率也会对应提高。)
运行时产生的性能数据越多,之后编译优化哪部分函数也就越有依据,所做的优化措施也就越多~。
这么一看,其实好像也没有什么太大问题,很正常。但是对应于硬件方面不怎么好的那就另说了
优化依据
jit解释运行一段时间后根据通过解释执行期间得到的相关运行数据进行针对性的优化操作
(常见的比如
一.某个函数运行一定次数达到编译为机器码的阈值会进行编译优化处理。当下次再次运行这个函数的时候会进行检查该函数是否编译为机器码。如果是机器码则转到机器码中去运行,如果不是则还是按照之前的JIT方式运行,这个时候函数还没有执行栈帧也还没有建立,新建立的栈帧逻辑是在机器码中的。
二.某个循环方法体内达到对应的执行次数阈值也会触发编译优化处理,这个时候的栈帧已经建立,想要切换到机器码运行会涉及到一个栈上替换的技术~。
优化过程
那么编译优化处理是怎么进行的呢?是通过单独的一个进程进行处理的,也就是dex2oat进程
何时编译优化
1.apk安装时(之前的系统中,Android在安装时就直接进行了最大等级的优化已达到应用运行速度却没想安装时间过长~)
2.JIT性能数据达到条件进行优化编译
已有策略:
编译优化并不是一股脑把所有的方法都进行优化了,而是分为不同的等级,dex2oat会根据不同的编译等级进行不同程度的优化(等级越高,编译耗费时间越长,之后运行效率也会越高)
编译优化等级最低的就是在APK安装时会让dex2oat进行优化处理,这部分只会将涉及到性能统计等相关的逻辑进行优化,其他逻辑还是按照JIT进行解释运行。
扩展点
充分利用系统idle时进行优化
针对于系统idle的时候但是又么有达到编译的那个条件你还不能编译,之后运行还是解释运行速度又慢~这里是一个优化点
动态配置优化方案。
我们可以根据产品的一些用户群体划分,从而下发不同的优化配置。
基于用户表现动态更新优化方案。
根据用户喜好等维度等划分下发配置优化策略
保证原有策略不受干扰
上面的优化策略当然是在dex2oat进程原有的编译等级上新增的方案,因此这部分优化逻辑在原生完成正常的工作流程后才会进行调度这些策略。
实现原理
降低编译阈值也就是门槛是不是会好一些,没错是的。包括最开始我们也是这样做的。但是这样是修改了全局的参数一样所有的方法编译门槛都被降低了,优先级高的进程可能还会抢在前面(优先级越低代表越重要),降低门槛意味着编译的请求会剧增,很大几率在做无用功(没有优先级)前台进程会更高几率卡死。所以这种方式被舍弃了
既然产生的统计数据不能达到条件,我们可以拟定一份通用的编译配置文件,在这些配置文件中这些函数将会按照进程,跑Monkey得出的数据,火焰图分析等进行再次编排出不同的优先级。(设定了编译函数的优先级和进程的优先级相比第一种好很多)
优化其实远不止编译方面的,包括GC通过threadlist在标记GC Roots过程中设置标志位,线程状态切换检测标志位实现暂停所有线程还有运行所有线程的操作。这部分也可以进行优化。线程会有个优化操作保存当前栈帧里面的引用型变量这些也是根对象,我们是不是可以保存在其他内存区域或者某个地方。
还有Linux的信号有很多,Android的SiganlCatcher只是检测了其中几个,有一个专门用于处理信号的类叫做FaultManager,我们需不需要根据自己业务去多加几个信号的监听做对应处理~
还有线程的TLAB缓冲区我们也可以进行动手脚。
借鉴系统的做法把一些常用的类编译为.art镜像文件,直接越过JIT。当然只限于系统APP,而我们做的就是这部分所以也可以进行修改
等等…
四、总结思考:
-
在探索的过程中,出现了很多不懂的问题,很多时候是对为什么这样设计的疑惑,在同事和领导的不吝教导和帮助下,这个项目达到了要求,团队的kpi保住了,项目交付出去了~~
-
Android源码中有很多有意思的设计,也许你会有很多疑惑:带着问题学习,请教同事,时刻保持学习的心态
当Anr时,trace中会有所有进程的信息。所有线程全部停止,全部运行是怎么实现的~
创建启动虚拟机时用到的那些类和我们App中自己创建的类有什么区别,第一种肯定会有优化那么具体怎么做的?
art文件,oat文件,dex文件 结构是怎样的。art中是否会存储oat文件的信息,oat文件是否会存储dex文件的信息
…
- 当然,优化还会继续进行探索,包括系统中很多点都是按照通用得出的一套方案,其实对应于不同的业务和不同的场景,需要自己去去了解这些原理然后做适合自己平台的一个优化。
学习一个新东西最好的办法我觉得是:
根据自己之前的技术栈和知识体系,罗列出涉及到的知识并且尽量将这些知识关联起来。
在这个过程中你可能会有些创新性的想法,尝试去做。做的过程中碰到问题再去学。
一遍一遍哪怕最后没有成功或者收效甚微,你也会有很大进步。
写在最后
当然,这上面是根据我们自己的系统做出的修改,并不适合所有的机器。
所谓没有最好,只有更合适。
如果你也想体验这些优化,可以去Android开发者平台中 查找配置基准文件来让你的APP享受这些优化吧~