通过对二进制数据重新排序,来提升app的启动速度
提升app的启动速度最常规的做法一般就是调整app启动时执行的代码,一般将可以后台进行的任务放到后台线程中执行,这样可以一定程度的提升app的启动速度(因项目决定)。
那么对于启动速度的提升还有很多方法,下面我来介绍一个通过对iOS项目编译后的二进制数据进行重新排序,来提升app的启动速度的方法。
一、分析app启动过程中,二进制数据排列顺序对启动速度的耗时所在。
首先一个进程在访问设备内存是,需要经过虚拟内存->物理内存的过程。虚拟内存在管理内存时是通过分页加载的,当进程访问一个虚拟内存Page而对应的物理内存却不存在时,会触发一次缺页中断(Page Fault),分配物理内存,有需要的话会从磁盘mmap读人数据。通过App Store渠道分发的App,Page Fault还会进行签名验证,所以一次Page Fault的耗时会经过如下过程:分配物理内存->磁盘IO->验签。
我们知道编译器在编译代码的时候,是按照Compile Sources中的文件顺序进行的,那么编译后的函数在虚拟内存中的排列如下:
Page1和Page2是在虚拟内存中的一个内存分布情况,按照编译后的顺序func1和func2被分配到Page1区域中,func3和func4被分配到Page2区域中。假设在app启动的时候调用了func1与func4两个方法,那么系统为了执行这两个方法,就必须进行两次Page Fault,这里就是耗时的所在。
所以我们要做的就是改变func4的顺序,最终排列的顺序如下:
二、重排二进制顺序
我们先来看看如果查看编译后的二进制函数分布情况,编译工程项目时,我们可以生成一份Link Map文件,首先打开iOS项目工程,选择Build Settings,搜索Link Map,
其中,Path to Linke Map File就是编译后生成Link Map文件的路径,Write Link Map File 标示是否写入文件,将其设置为YES,在编译时,就会在Path to Linke Map File目录生成对应文件。
我们先来看一个重排前的Link Map:
从上面可以看出,这些函数的排列顺序完全是按照Compile Sources中文件的顺序排列的,接下来就是我们来改变这些函数的顺序的时候了,如果要改变其顺序,我们就必须知道启动app的时候调用方法的顺序,我们可以根据项目源码一个一个的核对,但是这样做太费时间,如果项目比较庞大,我想你会眼花缭乱。这里我推荐一个第三方库:
AppOrderFiles,具体如果使用,可以参考这篇文章《App 二进制文件重排已经被玩坏了》,其原理是利用Clang中的代码覆盖率实现方案中SanitizerCoverage,可以通过查看clang的api中找到其具体用法。
其实就是clang为我们提供了一个方法,用于对代码中的每个函数、block,方法进行插桩,然后通过允许程序,在调用每个方法的时候,会在统一的一个方法中回到,在这个方法中,我们就可以得到调用的函数名称了,这里其实有点像runtime中的Swizzling。
开启 SanitizerCoverage 的方法是:在 build settings 里的 “Other C Flags” 中添加 -fsanitize-coverage=func,trace-pc-guard
。如果含有 Swift 代码的话,还需要在 “Other Swift Flags” 中加入 -sanitize-coverage=func
和 -sanitize=undefined
。所有链接到 App 中的二进制都需要开启 SanitizerCoverage,这样才能完全覆盖到所有调用。
然后在项目中必须实现以下函数:
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) { } void __sanitizer_cov_trace_pc_guard(uint32_t *guard) { // 我们就是通过调用clang函数,__builtin_return_address(0);获取到调用函数的信息 }
好了,我们已经生成了我们想要的order文件,如下:
这个就是我们app启动的时候,函数的调用顺序。
改变编译的顺序,我们可以生成一份Order File文件,然后在Xcode中的Build Settings中搜索Order File,配置好xxx.order文件的路径,如上,我们已经生成好了一个叫app.order的文件,配置如下:
接下来我们再次编译项目,然后查看Link Map文件,如下:
这一次编译后的顺序就是我们想要的顺序了。
三、查看Page Fault的情况
我们每做一次努力后,需要验证我们所做的效果如果,这是我们需要查看项目对二进制重排前后的Page Fault的对比,那么如果查看Page Fault呢?我们可以通过Xcode自带的Instruments,方法如下:
启动Instruments->system trace->,选择检测的app,启动录制,结束后,选择Virtual Memory Trace->By Operation,File Backed Page In (即:Page fault的信息)下选择对应的app。
那么这种方法对优化app启动速度到底能起到多大的作用,这要依你的app决定了,函数的调用情况不同,效果也是不一样。