iOS启动优化(上)-概念篇
前言
启动App
是给用户第一印象,如果启动比较慢,很可能会导致这个用户流失,那么启动时间的优化就显得尤为重要,本文结合启动时间从一些基本的概念入手分析
启动时间
启动根据main
分为两个阶段:main
函数之前(pre-main
)和main
函数之后,在pre-main
阶段,程序最早能执行的代码是+load
函数,但load
之前还有很多步骤,例如加载动态库的时间等,这些我们怎么去检测呢,这个时候就需要用到DYLD
检测,也就是通过配置环境变量,它会将这个阶段相关的耗时反馈给开发者。
- 新建个工程,然后cmd + shift + ,
-> Run
-> Arguments
配置环境变量DYLD_PRINT_STATISTICS
,也可以设置DYLD_PRINT_STATISTICS_DETAILS
这个会详细些。本文配置DYLD_PRINT_STATISTICS
来讲
- 在真机上运行,结果如下:
- 这个代表本次pre-main
时间用了338.68
毫秒:
- dylib loading time
:是动态库载入耗时。系统提供的动态库已经载入在共享缓存空间,已经做了优化,但自定义动态库并没有这个优化,苹果建议创建的动态库不能超过6
个,如果大于这个数就需要做动态库合并
- rebase/binding time
:是重定位/绑定耗时,涉及到动态库合并
- ObjC setup time
:OC注册类。在做项目时,可以删除废弃的(不使用的)类,只要在里面就会影响启动时间
- initializer time
:执行load
、构造函数
的耗时。
- slowest intializers
:最慢的动态库耗时
下面着重讲rebase/binding
相关的知识
物理内存和虚拟内存
物理内存
- 在早期的操作系统,
CPU
直接从内存条读取数据,这就导致了内存不够用
问题,如下图:
- 应用需要加载时就会直接加载到内存条中,然后CPU
去内存条读取。当加载的应用多了,再加载新的应用时内存就不够用
了,这时候就需要干掉一些应用然后再去打开这个新的应用。
- 加载到内存条中的应用都是根据内存地址去读,那么如果通过一些外挂加载到内存,然后可以在遍历内存条中地址访问到其他应用的内容,就导致账号被盗等安全问题,所以这个方式也是不安全
的
- 怎么解决这些问题呢?工程师们发现加载到内存中的应用,大多数之用到一小部分功能,这就导致了资源的浪费,于是就产生了懒加载
。懒加载是引用加载到内存中时,先加载启动相关的,后面要用到就再加载到内存条。
这样虽然会减少内存的占用,但是应用接下来的内存不知道要分配到哪,这样就导致代码不连续
,需要在运行过程不断计算地址很不方便
,而且效率很低
,于是工程师们就创造了虚拟表
,也就引出了虚拟内存
虚拟内存
- 有了虚拟表,应用程序就只读代码,而代码计算地址的事情交给
CPU
和硬件MMU
,MMU
是内存管理单元
,它只做一件事:翻译地址
:
- 这样应用程序的在运行时访问的内存就是连续的,而访问的内存就是虚拟内存
,虚拟内存对应的就是计算好的物理地址。
- 虚拟地址和物理地址不是
一个字节一个字节对印的,这样效率就很低。由于现在的应用内存是一块一块的,干脆就以块为单位去对印,单位就是page
,此刻就产生了内存分页管理的概念
映射表(内存分页)
- 页的大小在不同的操作系统中是不一样的。在
iOS
中(64位)
一页是16K
,在MAC
中是4K
。在MAC
中可以使用环境变量PAGESIZE
查看:
- 现在
内存连续
的问题得以解决,安全问题
也解决了。由于一个应用只访问自己的虚拟表,而翻译出来的物理地址也是固定的,所以那些外挂就无法访问其它应用。内存溢出
的问题也解决了,因为现在内存都是一页一页的访问,所以就不会产生溢出。 - 当应用加载时,首先启动需要的代码会直接加到内存,当要执行新的代码时,
cpu
发现这段代码内存中没有就会把代码卡住,然后操作系统会将这页加到物理内存中,这个现象就叫做缺页中断(pagefault)
。 - 操作系统需要执行的一页代码载入到物理内存中时,会往空缺处插入。但手机启动后,物理内存就没有空位了,里面都放了其它的一些数据,但到底往哪里加呢,这个由操作系统决定。操作系统提供一个算法:
页面置换算法
,它会覆盖掉
不那么活跃的部分。所以有时候多打开些应用后,再去进入第一次打开的应用时会重新启动。 - 有时候在访问内存时会访问比
App
当前大的内存,因为虚拟表有8G
大小,能访问的有4G
,那么他会访问到其它应用吗?不会,这块虚拟内存是没有数据,它指向NULL
:
rebase/binding
-
binding
是绑定,当内部文件访问外部方法,就要通过内部符号绑定访问外部,现在的绑定方式都是懒加载
-
rebase
是重定位。由于虚拟内存产生后,每次内存都是从0
开始,这个时候相应的安全就不存在了,所以操作系统就出现了一个新技术ASLR
:让每次生成的虚拟页面,不要从0开始,从随机的值开始,应用的每次启动的起始位置都是随机的。此时行数的位置就成了ASLR+OFFSET
,也就是rebase
重定向 -
一页的缺页中断是感知不到的,时间是毫秒级别,基本感知不到。但同时有大量的缺页中断,这个时候用户就能感知到了,冷启动时就会产生大量的缺页异常。如下面例子:
- 重签名一个
微信7.0.8
的ipa
,然后跑到自己的工程。运行起来后Cmd+i
打开instruments
,然后选择System Trace
,搜索Main Thread
,之后选择Virtual Memory
File Backed Page In
可以看到缺页中断的个数以及所用的时间:
- 通过观察发现消耗的总时间中,
PageFault
占绝大部份 - 再启动一次发现耗时要少的多,此时应用的物理内存还没被覆盖,所以启动会时间会少很多
冷启动
:应用在物理内存中没有占用时的启动是冷启动。例如第一次打开App
,或者APP
被杀死后,一段时间过后再打开,都是冷启动-
热启动
:应用编译在物理内存中的内存还存在的启动就是热启动,例如短时间APP
从后台返回,APP
杀死后立即打开,此时它的物理内存还存在,此时就是热启动。 -
如果减少
PageFault
的个数,就会达到优化的目的,具体操作下篇文章再进行讲解