启动优化理论知识
启动过程使用main函数作为分界点分为两部分,main函数之前(premain),main函数之后。
iOS监测 - premain
DYLD的反馈来监听main函数之前阶段,操作步骤
1、配置环境变量
DYLD_PRINT_STATISTICS
如图:
2、运行到真机上
dylib loading time : 动态库载入(减少动态库的载入,苹果的建议是不大于6个,如果大于让多个动态库合并)
rebase/binding time : 重定位/绑定 (与MachO文件相关,虚拟内存)
Objc setup time : OC类注册(减少OC类的定义,无论类是否被使用,只要存在在工程里面,就会造成启动时间的消耗)
initializer time : 执行load、构造函数的耗时
虚拟内存
虚拟内存的发展历史:
早期当应用程序加载时,直接将可执行文件全部放到内存里面。缺点:1、内存不足 ; 2、安全问题;
这时工程师发现,用户一开始的时候并不会使用所有的数据,因此没必要一开始就把全部数据加载到内存中,所以可以使用懒加载的方式,按需要加载,这样就节省了内存;
这时候又有问题了,如果按照懒加载的方式,那么代码在内存中的物理地址就不连续了,每次寻找地址都需要进行计算,会非常耗时;
这时候引入MMU(内存管理单元)--代码映射表(虚拟地址与物理地址的映射),代码对应虚拟地址,是连续的,通过映射表将虚拟地址翻译成内存中的物理地址,但是即使是翻译速度很快,但是颗粒度太小,还是比较耗时;每一个应用程序都有一个单独的映射表。
这使用引入了分页(Page)的概念,iOS 中一页是16k,Mac中一页是4k(终端输入 PAGESIZE可以查看)。颗粒度增大,节省计算。
iOS 的虚拟内存大小为4G.
xcode中打印内存地址的命令:
dis -s 0x00000
缺页异常(缺页中断):当物理内存中没有加载程序使用的内容,就会发生缺页中断,操作系统会将程序加载进物理内存中。
页面置换:覆盖掉不活跃的部分。这也就解释了,为什么有的app打开的时候不要重新加载,有的app却需要重新加载的现象。
虚拟内存可以大于物理内存的原因,虚拟内存中未使用的部分统一指向物理内存中很小的一部分值为空的空间。
虚拟内存8个G,只能访问4个G: 为了兼容32位(CPU上的数据总线数量)操作系统,64位的应用程序虚拟内存地址从0x0000 0001 0000 0000开始访问,32位从0x0000 0001开始。每个进程看起来都有4G。
rebinding 绑定(懒加载绑定),当使用外部函数的时候发生。
rebase 文件的访问,在内存中是随机的,这增加了安全性;但是虚拟内存出现之后,这种安全性就不存在了,因为虚拟内存都是从0开始连续的,只需要计算出文件在虚拟内存中的文件偏移就可以访问应用程序的数据了。
于是引入了一个概念 ASLR
让应用程序每次启动的时候都给一个随机的起始地址。这样别人就找不到文件在虚拟内存中的位置了。
函数在MachO中都有一个偏移量offset,那么 ASLR+offset这个操作就叫做 rebase。
启动优化实践
二进制重排
当冷启动应用的时候,会发生很多缺页中断(pagefault),而中断的耗时是非常久的,因此需要进行优化,优化的目的就是减少缺页中断,优化的手段就是二进制重排(将启动时刻需要调用的方法排到前面)。
查看缺页中断的次数,可以通过xcode的Instruments工具来查看。
具体步骤:
1、设置link map file
2、在项目根目录下,创建编辑.order文件,并加入到xcode的配置中
3、编译检查link map file二进制重排的效果就可以了。
但是这其中还有很大的问题,如果要编辑order文件,那么就需要知道要调用所有的方法名称,包括嵌套方法,block,swift混编等等,这就很麻烦了,具体该怎么解决呢?接下来继续学习Clang插桩。
其他资料
iOS基于二进制重排启动优化 这篇博客写的很不错,在缺页中断的解释以及监听缺页中断的操作上比我的要完善很多,另外还写了关于Clang插桩的内容。