性能优化-应用启动时间

2020-03-24  本文已影响0人  GitArtOS

性能优化-应用启动时间

设置环境变量

这里的应用启动时间指,应用启动到显示第一个页面展示时的时间。

应用启动有冷启动和热启动,热启动是指应用在后台活着,然后再启动应用。这里只谈冷启动。

启动时间在小于400ms是最佳的,因为从点击图标到显示Launch Screen,到Launch Screen消失这段时间是400ms。启动时间不可以大于20s,否则会被系统杀掉。

Xcode11,通过添加环境变量可以打印出APP的启动时间分析(Product- scheme-Edit scheme -> Arguments-如下),DYLD_PRINT_STATISTICS设置为1,如果查看更详细的信息可以DYLD_PRINT_STATISTICS_DETAILS设置为1。

截屏2020-03-24上午11.48.34.png 截屏2020-03-24上午11.48.34.png

跑下工程:

Total pre-main time: 6.3 seconds (100.0%)    
 dylib loading time: 2.1 seconds (34.7%)     
rebase/binding time: 3.7 seconds (59.5%)        
 ObjC setup time:  70.06 milliseconds (1.1%)    
  initializer time: 287.55 milliseconds (4.5%)     
slowest intializers :
...........

可以看到,在执行main函数前,应用准备了执行了4个流程:dylib loadingrebase/bindingObjC setupinitializer,下面我们将好好分析这几个流程。

加载dylib
分析每个dylib(大部分是iOS系统的),找到其Mach-O文件,
打开并读取验证有效性,找到代码签名注册到内核,
最后对dylib的每个segment调用mmap()。
rebase/bind
dylib加载完成之后,它们处于相互独立的状态,需要绑定起来。
在dylib的加载过程中,系统为了安全考虑,引入了ASLR(Address Space Layout Randomization)技术和代码签名。
由于ASLR的存在,镜像(Image,包括可执行文件、dylib和bundle)会在随机的地址上加载,和之前指针指向的地址(preferred_address)会有一个偏差(slide),dyld需要修正这个偏差,来指向正确的地址。
Rebase在前,Bind在后,Rebase做的是将镜像读入内存,修正镜像内部的指针,性能消耗主要在IO。
Bind做的是查询符号表,设置指向镜像外部的指针,性能消耗主要在CPU计算。
OC setup
OC的runtime需要维护一张类名与类的方法列表的全局表。
dyld做了如下操作:
对所有声明过的OC类,将其注册到这个全局表中(class registration)
将category的方法插入到类的方法列表中(category registration)
检查每个selector的唯一性(selector uniquing)
如果在各个 OC 类别的 ‘load’方法里做了不少事情(如在里面使用 Method swizzle),那么这是pre-main阶段最耗时的部分。dyld运行APP的初始化函数,调用每个OC类的+load方法,调用C++的构造器函数(attribute((constructor))修饰),创建非基本类型的C++静态全局变量,然后执行main函数。

优化整体思路

1. 移除不需要用到的动态库
2. 移除不需要用到的类
3. 合并功能类似的类和扩展
4. 尽量避免在+load方法里执行的操作,可以推迟到+initialize方法中。

在了解了应用启动流程后,那对应用启动优化的工作就细分到了对每个流程的优化上。
<a name="LeY0o"></a>

1. load dylibs加载动态库

<a name="AdEI7"></a>

启动的第一步是加载动态库,加载系统的动态库使很快的,因为可以缓存,而加载内嵌的动态库速度较慢。所以,提高这一步的效率的关键是:减少动态库的数量。合并动态库。

<a name="P6Oqv"></a>

2. Rebase & Bind & Objective C Runtime

<br />Rebase和Bind都是为了解决指针引用的问题。对于Objective C开发来说,主要的时间消耗在Class/Method的符号加载上,所以常见的优化方案是:

1)减少__DATA段中的指针数量。

2)合并Category和功能类似的类。比如:UIView+Frame,UIView+AutoLayout…合并为一个<br />删除无用的方法和类。

3)多用Swift Structs,因为Swfit Structs是静态分发的。
<a name="1nmJG"></a>

3. Initializers

<br />通常,我们会在+load方法中进行method-swizzling,但这会影响应用启动的时间。

1)用initialize替代load。不少同学喜欢用method-swizzling来实现AOP去做日志统计等内容,强烈建议改为在initialize进行初始化。

2)减少atribute((constructor))的使用,而是在第一次访问的时候才用dispatch_once等方式初始化。

3)不要创建线程

4)重写代码。
<a name="gvLVs"></a>

4. main函数之后

优化的核心思想:能延迟初始化的尽量延迟初始化,不能延迟初始化的尽量放到后台初始化。

我们首先来分析下,从main函数开始执行,到你的第一个界面显示,这期间一般会做哪些事情。

在这个过程中我们可以借助工具来进行检测

CFTimeInterval startTime = CACurrentMediaTime();<br />//执行某个方法<br />CFTimeInterval endTime = CACurrentMediaTime();<br />当检测出耗时的模块时,就可以按照优化的核心思想来进行处理了;

能延迟初始化的尽量延迟初始化,不能延迟初始化的尽量放到后台初始化。

main以后的优化思路

梳理各个三方库,找到可以延迟加载的库,做延迟加载处理,比如放到首页控制器的viewDidAppear方法里。
梳理业务逻辑,把可以延迟执行的逻辑,做延迟执行处理。比如检查新版本、注册推送通知等逻辑。
避免复杂/多余的计算。
避免在首页控制器的viewDidLoad和viewWillAppear做太多事情,这2个方法执行完,首页控制器才能显示,部分可以延迟创建的视图应做延迟创建/懒加载处理。
采用性能更好的API。
首页控制器用纯代码方式来构建。
上一篇下一篇

猜你喜欢

热点阅读