余额不足.

记录一次APP启动优化

字数统计: 750阅读时长: 2 min
2018/04/11 Share

项目日渐庞大,代码越来越烦杂,性能问题也越来越多,准备搞一次系统性的优化。
随着加载项的越来越多,app启动时感觉有很明显的卡顿现象。
要做优化,先得分析APP是如何完成一次启动的

1
t(App总启动时间) = t1(main()之前的加载时间) + t2(main()之后的加载时间)。

So,我们先分析下main()加载之前的过程。

target中添加一个value为1的DYLD_PRINT_STATISTICS环境变量。

run一哈,控制台输出

1
2
3
4
5
6
7
8
Total pre-main time: 532.27 milliseconds (100.0%)
dylib loading time: 156.04 milliseconds (29.3%)
rebase/binding time: 74.47 milliseconds (13.9%)
ObjC setup time: 155.44 milliseconds (29.2%)
initializer time: 146.19 milliseconds (27.4%)
slowest intializers :
libSystem.B.dylib : 15.71 milliseconds (2.9%)
libMainThreadChecker.dylib : 18.48 milliseconds (3.4%)

word妈耶!!! 在main()执行之前居然就用了532.27ms(需以冷启动的耗时为基准)。

从日志上可以看到大致的过程:

1
2
3
4
加载dylib
Rebase && Bind (ASLR指针地址偏移,Rebase修正内部指针指向,Bind修正外部指针指向)
初始化Objective C
其它的初始化代码

根据以上过程能得出影响pre-main()耗时的主要因素如下

1
2
3
* 动态库加载越多,启动越慢。
* ObjC类越多,启动越慢
* +load越多,启动越慢

那么在pre-main()这一步我们都能做些什么优化工作?

加载dylib
在这一步我们能优化的不多,无非是减少一些动态库的依赖。

Rebase && Bind
因为ASLR的存在,可执行文件在虚拟内存中的地址每次启动都是不同的,所以需要根据偏移量来计算正确的地址。这一步可以优化的点是移除不用的类和减少Catagory和selector的数量。

可以使用FUI来寻找没有使用到的类,也可以使用AppCode来寻找,这里推荐使用AppCode,能相对安全的移除无效引用和方法等。当然,命中之后也得小心移除,以免出现意外。

上图为未优化前执行结果

上图为优化之后执行结果
经过一系列瞎比操作之后,删除了一些不必要的第三方库,大概移除了70+的无效类,900+无效方法,有些意外的是未使用的属性反而增加了。pre-main大概优化到了330ms左右,虽然优化到了推荐的400ms以内,但还是差强人意,待继续优化。

1
2
3
4
5
6
7
8
9
Total pre-main time: 329.34 milliseconds (100.0%)
dylib loading time: 90.18 milliseconds (27.3%)
rebase/binding time: 58.19 milliseconds (17.6%)
ObjC setup time: 56.01 milliseconds (17.0%)
initializer time: 124.79 milliseconds (37.8%)
slowest intializers :
libSystem.B.dylib : 20.23 milliseconds (6.1%)
libBacktraceRecording.dylib : 10.15 milliseconds (3.0%)
libMainThreadChecker.dylib : 33.22 milliseconds (10.0%)

初始化Objective C
上一步优化完之后这里也就没啥可做的了

其它的初始化代码
这一步开始动态调整
使用+initialize替换+load,从而加快所有类文件的加载速度.

CATALOG