APP的启动过程
冷启动:APP在完全杀死的状态,需要系统新创建一个进程分配给它的情况。这是一次完整的启动过程。(二进制重排主要针对冷启动的优化)
热启动:App 在冷启动后,用户将App 退到后台,即在App的进程还在系统里的情况下,用户重新启动进入 App 的过程,这个过程做的事情非常少,启动速度非常快。
启动时间的组成
启动时间得可以根据main()函数前后进行划分,大致分为以下时间段。
1.t1阶段,main()之前的处理所需时间,称为pre-main。
2.t2阶段,main()及main()之后处理所需时间。
3.首屏渲染完成后。
测量 pre-main 时间
Edit scheme -> Run -> Auguments 添加环境变量 DYLD_PRINT_STATISTICS,value设为YES(图1)。
图1然后启动项目,就可以看到各方面的时长(图2)。
图2由图2可以知道 t1阶段:pre-main流程如下(图3):
图3加载dylib:
加载可执行文件(App 的.o 文件的集合),加载动态链接库。
在dylib的加载过程中系统为了安全考虑引入了ASLR(Address Space Layout Randomization)技术和代码签名。
ASLR技术:镜像Image、可执行文件、dylib、bundle在加载的时候会在其指向的地址(preferred_address)前面添加一个随机数偏差(slide),防止应用内部地址被定位。
rebase/bind
dylib加载完成之后,它们处于相互独立的状态,需要绑定起来。
rebase:将镜像读入内存,修正镜像内部的指针,性能消耗主要在IO。(结合ASLR偏移量,计算好地址)。
bind:查询符号表,进行方法绑定,修正镜像内部的指针。
Objc Set Up
Objc 运行时的初始化处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;
initializer
initializer:初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。
总结:结果上面的分析,得到得优化方案有:
1.减少动态库加载,每个动态库都有其的依赖关系。
2.减少加载启动后不会去使用的类或者方法。
3.+load方法的内容尽量在加载完成后执行,或使用 +initialize()方法替换掉,尽量进行懒加载。
注意:在进行二进制重排之前我们还需要知道内存是怎么操作的。
内存管理
内存是分页管理的,映射表(虚拟内存)不是以字节为单位,是以页为单位的。macOS,Linux一页的大小为4k,iOS是一页大小为16k。
物理内存
早期的计算机没有虚拟内存这一说,系统的程序运行都会全部加载到内存中,这样子就会造成了大量资源的浪费。如果内存不够,程序将无法打开。必须先关闭前面的部分应用才能继续开启,这样子性能就会存在很大的问题。其次,因为程序放到了物理内存且内存地址是连续性的,别人通过其他运行中的软件访问到物理内存的话,就会跨程序访问到其他程序的物理内存,找到对应的点进行攻击,这里也是一个非常大的安全问题。如图4:
图4虚拟内存
针对物理内存的种种问题与弊端,工程师们就提出了虚拟内存,完美的解决了以上的问题。首先我们的程序加载时候会进行模块化(分页)处理,生成了一张 虚拟内存和物理内存关联的表 --映射表(地址为0 - 4G)。图5:
图5当App需要使用某一块虚拟内存的地址时,会通过这张表查询该虚拟地址是否已经在物理内存中申请了空间。如果已经申请了则通过表的记录访问物理内存地址,如果没有申请则申请一块物理内存空间并记录在表中(Page)。这个通过进程映射表映射到不同的物理内存空间的操作叫地址翻译,这个过程需要CPU和操作系统配合。(注意:分页的首地址不是从映射表的0开始的,虚拟内存地址分配还会涉及到 ASLR)
分页异常(Page Fault)
在分页加载的过程中,当分内没有存在内存的时候,系统就会阻塞该进程,将磁盘中对应Page的数据加载到内存,把虚拟内存指向物理内存。在启动的过程中会存在大量得分页异常,就会重复大量的做阻塞和重载操作,那么为了解决这问题,就引入了二进制重排。
补充:如果我们的内存加载满了,内存就会根据程序的活跃度来释放分页。平时我们放久的微信重新进去就会被移除内存,会出现冷启动的现象。
二进制重排
重排目的:二进制重排就是为了减少启动时因为分页异常而浪费的时间。
没重排 已重排由上可知,二进制重排主要是启动时必须要加载的page放到前面优先加载,这样子就会减少分页缺失的发生,从而达到优化的效果。
查看 Page Fault
Instruments -> System Trace
File Backed Page In 即为 Page Fault 的个数。
链接映射文件 Link Map File
sections部分信息如下:
由section信息可里可以看到启动时方法符号的排序。
order_file的引入
Xcode提供了排列符号的设置给开发者,设置 order_file 即可。objc 源码就采用了二进制重排优化。
order_file的配置
在根目录生成link.order文件,这里面就是方法符号的排序。
Target -> Build Setting -> Linking -> Order File 设置 order file 的路径
order file编辑
注意:全手写一定是不可取的,想实现自动化就要解决下列问题:保证不遗漏方法,保证方法符号正确和保证方法符号顺序正确。
解决方案可见 《抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15%》
如果想了解编译期插桩的话请参考:https://juejin.cn/post/6844904170139418631。
网友评论