热修复设计之AOT-JIT&dexopt-与-dex2oat-(一)

带着这个问题再考虑很多资料(包括wiki)对JIT的另一个描述,JIT是在运行时将解释执行的语言(比如字节码)编译成机器指令,以提高运行速度。这个看法在前面的某篇也提过,的确很多JIT编译器,比如java的就是这么干的(我们下面就拿java举例),但是,既然字节码编译成机器指令可以提高速度,为何一定要放在运行时进行,做成AOT模式不是可以运行得更流畅吗,而且还能一次编译,N次执行,为啥非要做成运行时做,JIT本来是要提高运行速度,但这岂不是降低了效率?

这种看法是有道理的,事实上,java的确有一些AOT编译器,可以将字节码甚至java源码直接编译成机器指令的可执行文件,微软当初的VJ++似乎就这么搞的,和sun打了很久的架,sun还喊出了pure java(纯粹的java,即按照sun的设计理念和标准来实现java)的口号,有兴趣可以去搜一下这段历史,挺搞笑的

另一方面,sun的jvm虽然采用了JIT编译,但同时也提供了client和server模式,在server模式下,虚拟机在一开始执行的时候会先尽可能多地对字节码进行编译,且优化程度也尽量高,这样可以使得服务器在运行过程中能尽量少卡顿,根据上面的讨论,这实际上相当于AOT批处理了。client模式下则不会这样做,主要是为了尽量缩短启动延迟,提高用户体验

顺便说一句,对于JIT将字节码编译成机器指令,wiki的描述比较暧昧,有时候用machine code,有时候用native code,比方说我们用java实现一个A语言的虚拟机,解释A的字节码执行,并将字节码编译成java自己的字节码,这也是JIT,因为A跑在jvm上,则java字节码就看做是native code,而machine code这个machine也不见得是真实机器,jvm也是一种机器

由于JIT编译耗费运行时间,则对于某些优化点就无法做到百分百支持,必须在代码优化和执行卡顿之间做一个权衡,AOT就没有这个问题,另外,AOT可以做到编译后持久化到存储,而JIT一般是每运行一次就会搞一遍重复的编译

如果我们不考虑AOT本身耗费的时间(比如编译一次,N次运行),也不考虑使用上的方便性(AOT可能会有多次编译过程),那是不是可以认为,AOT编译可以完全替换JIT编译,JIT就完全没必要了,实际情况当然不是这样,JIT还是有它的优势和必要性的,否则研究它的那群人岂不都是傻子

从动静态来看这个问题,AOT是静态编译,而JIT是运行时动态编译,则JIT的优势在于,它不但能看到静态信息(代码),还能看到运行时的情况,这就是JIT的优势。接下来讨论的JIT是一种狭义的JIT,即在AOT搞不定的地方使用的JIT,而非上述形式上的

关于JIT的优势,wiki上给出了四点理由,但有意思的是,其中有两条连它自己都承认并非只有JIT能做,也就是说至少理论上,用AOT实现(或部分实现)是可行的,这四条是: 
1、JIT可以根据当前的硬件情况实时编译成最优机器指令,比如cpu中如果含FPU,MMX,SSE2,或者Intel cpu的并行计算特性,则可以做到同一份字节码,在不同机器运行时最大限度利用硬件资源。而如果是AOT编译一个程序放出去给不同用户使用,就只能去兼容特性最少的cpu,或者内部实现多个版本 
2、JIT可以根据当前进程实际运行状态,将字节码编译成适合最优化的机器指令序列。wiki认为静态编译也可以通过分析profile来实现这方面的优化(可能有点麻烦) 
3、当程序需要支持动态链接时,即在静态编译阶段,可能不知道运行时会引入什么样的代码来和程序协作执行,这时候就只能依靠JIT 
4、考虑到垃圾收集,JIT可以根据进程中的内存实际情况来调整代码,使得cache能更充分地使用,wiki认为静态编译也可以做到,但JIT做起来更容易实现

对于第一条,JIT的确可以实现这种优化,但是AOT一样可以实现,虽然AOT编译一个程序给不同用户执行无法做到,但是可以编译字节码发布,用户使用时再根据当前机器再做一次AOT 
对于第二条,首先我认为大多数程序的运行状态不会经常变动,比如同一个程序有时候是整数计算居多,有时候是浮点计算居多,一般来说程序应用场景是固定的;其次对于特定场景也可以AOT 
对于第三条,的确动态链接的全文静态优化AOT无法做到,但是如上篇所说,必要时候我们可以直接砍掉语言的动态性,再者静态编译时候也不是什么都感知不到,比如C语言做静态链接时,至少是知道头文件的,动态性没那么强 
对于第四条,AOT也是有可能实现的,虽然麻烦很多。另一方面,静态编译时也有指令乱序来提高cache使用效果,再者这块也和垃圾收集算法、程序本身的局部性有很大关系,如果程序本身写的烂,这个调整效果可能也比较有限

所以我觉得,这四条虽然都有道理,但没精确说到点子上。再来审视这个问题,我们可以看出,从理论上讲,AOT可以完全代替JIT,因为一个进程的状态是有限的,AOT可以预测所有可能情况并进行优化,实际运行时的状态不会超出AOT的预测,采用最优代码执行即可,而JIT在这里的优势就是,它能精准地得知运行时状态,而不是像AOT那样预测,成本更低,如果一个AOT优化的成本过高,则应该选择JIT。AOT不是不能做,而是不可行

JIT相关的资料,相比wiki我更推荐这篇论文:《Representation-based Just-in-time Specialization and the Psyco prototype for Python》 by Armin Rigo,这个论文是以python和其JIT插件库psyco为例来分析,论文题目中的单词Specialization可谓画龙点睛,它指出至少在动态类型语言中,JIT的关键作用之一是特化,用上篇的话说,就是动态行为静态化,而这些场景中AOT不可行的原因是它很难找到特化的方向,而枚举所有特化是不可行的

一个典型的特化案例,也是论文中提到的,假设有一个函数f(x,y),则对于x的输入x1,x2,x3…,我们可以特化这个函数为f1(y),f2(y),f3(y)…,其中fk(y)在功能上对应f(xk,y),这样一来,每个fk可以单独地做优化,与其他函数无关,而特化后的函数列表至少不会比原来的f(x,y)慢。唯一的问题是,x的取值可能很多,比如x是一个int,则如果采用AOT方式来特化,则需要编译42亿多个函数,这显然是不现实的,但是JIT就有可能对这个场景做优化,原因在于,x的取值虽然很多,但在一个具体运行过程中范围相对小,甚至是很小,这符合二八定律

于是,在运行时我们可以对函数f做监控,统计每次输入的x的值,如果发现这些值的分布不平均,比如x为123的情况占大多数,则动态特化一个f123(y),对其进行高度优化,然后修改f函数为:

func f(x, y):
if x == 123:
return f123(y)
… //f的正常流程

于是只需要一个特化函数,就能带来运行时效率的提升,这就是JIT特化的优势

对很多程序来说,对这种数值做监控和特化可能性价比不高,因为不是每个函数的输入值范围都呈现不平衡状态,或者说不是那么明显,但上面这个例子中,x和y不一定是变量,也可以是类型,这样一来对动态类型语言就有很大的意义

前面讲过,在C++中可以用模板来实现鸭子类型,实质是通过代码替换来实现类型静态化,C++这个方式虽然效率高,但渠道是通过静态编译中的全文分析,是AOT编译,如果改成稍微动态性强一些的语言,就用不上了。在动态类型中,一个函数如果有k个参数,有n个可能类型,则AOT需要将一个函数扩展为n^k个特化实例,n和k稍大一点就不可操作了,何况本身就是动态类型,n的范围都不一定在编译期能知道

对这种场景,JIT就可以通过统计的方式来选择性地特化,这个的可行性和现实意义更大,原因在于,程序员在用动态类型写程序的时候,比如写一个函数:

func f(x, y):
return x + y

理论上,这个函数可以接受任意类型的x和y,只要x能和y相加即可,但具体到一个确定的程序,这个函数的业务意义一般是固定的,或者是做字符串拼接,或者是数值相加,很少说写一个函数,接收八竿子打不着的不同的类型还能运算,而且还是程序员刻意这么设计,就像前面讲过的C++模板的二义性一样,基本见不到这种需求,所以在函数的输入参数类型上,符合二八定律。于是对于上述代码,假设x和y绝大多数情况下都是整数,则进行特化(假设这个伪代码中不考虑整数溢出):

func f(x, y):
if not (x instanceof int and y instanceof int):
//有一个不是整数,走原有流程
return x + y
//整数加法的特化流程
internal_code:
int ix = get_internal_int(x)
int iy = get_internal_int(y)
int iresult
asm:
push … //当前状态压栈
mov eax, ix
mov ebx, iy
add eax, ebx
mov iresult, eax
pop … //状态出栈
return build_int_object(iresult)

当然这只是个例子,如果只是为了一个加法,这多少有点小题大做,但如果f的逻辑较为复杂,优化就很明显了

还可以逆向思维一下,AOT难以实现特化的原因是无法考虑所有情况,但我们也没有必要考虑所有情况,实际上类型使用的二八定律本身也在另一个二八定律里,具体到int类型,一个绝大多数使用到的类型都是int的程序在所有程序中占绝大多数,至少在一个有限的领域是这样,因此干脆对于每个函数都只做int相关的特化,这样2k种情况还算能接受(实际情况数比2k低很多,因为很多参数如果被假定为int,会语法错误,就不用假设了),如果再做的好一点,还可以做成编译器选项,由用户来指定AOT的时候对哪个类型特化,这样就比较完美了

除类型的动态性外,其他动态性也可以类似讨论,仅拿上篇的例子,不赘述了:

for i in range(n):
print(i)
转换为: 
if not (range is builtins.range and print is builtins.print):
for i in range(n):
print(i)
else:
internal_code:
long tmp = get_internal_long(n)
long i
//这里应该用汇编,仅表个意思
for (i = 0; i < tmp; ++ i):
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

跨平台开发:Flutter.png

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

W9KwB-1712472096707)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值