iOS性能优化专题

iOS原理 App的启动优化2:二进制重排

2020-12-05  本文已影响0人  东篱采桑人

iOS原理 文章汇总

前言

iOS原理 App的启动优化1:优化建议一文中已经介绍了启动优化的相关概念,我们知道,通过二进制重排可以减少App的启动时间,提高程序的启动性能。

二进制重排原理

CPU访问进程数据时,先访问数据对应的虚拟内存page,通过虚拟内存地址找到其对应的物理内存地址,再通过物理地址访问到物理内存上的数据。如果对应的物理内存地址不存在,说明这部分数据没有加载到物理内存中,此时会触发缺页中断(Page Fault)

物理内存和虚拟内存的详细介绍可阅读iOS原理 物理内存&虚拟内存

Page Fault会中断当前进程,需要先将访问的数据加载到物理内存中,再让CPU访问。而通过App Store渠道分发的App,Page Fault还会进行签名验证,所以每一个Page Fault都会带来一定的耗时。

如果启动过程中触发大量的Page Fault就会降低启动性能,延长启动时间。通过System Trace可以查看App在启动过程中触发的Page Fault次数:

可以看到,启动过程中触发了60次Page Fault,总共耗时7.73ms。这个案例Demo只是新建的一个空项目,一般来说,工作项目会触发上千次Page Fault,就拿微信来说,触发次数达到2600多次,耗时接近700ms。因此,减少启动过程中Page Fault的触发次数,就能缩短启动时间,提高启动性能,而这就可以通过『二进制重排』来实现

二进制重排实现方式

App启动过程中会调用一些方法和函数,CPU需要访问相关数据。这时,通过修改代码在二进制文件的布局,将启动时刻调用的方法和函数的二进制符号,排列在一起,确保在一个虚拟内存page中,这样就从多个Page Fault减少为一个Page Fault,这就是二进制重排

修改方法和函数二进制符号的布局,需要通过Linkmapld以及Clang插桩来实现。

1. Linkmap

Linkmap是iOS编译过程的中间产物,记录了二进制文件的布局,需要在Xcode中找到Target -> Build Settings -> Write Link Map File,并设置为Yes来开启。

Linkmap主要包括三大部分:

编译器在生成二进制代码的时候,默认按照链接的Object File(.o)顺序写文件,按照Object File内部的函数顺序写函数,因此方法和函数编译后的二进制符号,默认先按照.o文件(Object File)的链接顺序,再按照文件里的编写顺序来排列

以案例Demo为例,查看编译后方法和函数在Linkmap里面的排列顺序。

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

void test1(){
    
    printf("1");
}

void test2(){
    
    printf("2");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    test1();
}

+(void)load{
    
    printf("3");
    test2();
}

@end

Link Map File里的布局情况可以印证,方法和函数编译后的二进制符号,是先按照.o文件(Object File)的链接顺序,再按照文件里的编写顺序来排列。由于启动过程中调用的方法和函数可能存在于不同的类里,它们编译后的符号默认在二进制文件里分散排列,调用时就会触发大量的Page Fault

2. ld

ld是Xcode使用的链接器,写入其参数order_file中的符号,会按照写入顺序排列在二进制文件中符号区域的顶部。因此,在Xcode中,通过Target -> Build Settings -> Order File来配置一个后缀为.order的文件路径,并在这个order文件中,将启动过程中调用的方法和函数以符号格式写在里面,在项目编译后,这些符号就会按照文件里的顺序排列在二进制文件中。若order文件中的符号对应的方法实际不存在,ld则会忽略这些符号。

3. Clang插桩

Clang插桩,即批量hook,借助SanitizerCoverage(llvm内置的一个简单的代码覆盖率检测),实现100%符号覆盖,获取到所有的swiftOCCblock函数

Clang插桩覆盖的官方文档 : clang 自带代码覆盖工具

实现步骤如下:

二进制重排的案例Demo

通过上面的学习可知,二进制重排的实现步骤如下:

接下来通过案例Demo来详细讲解实现步骤,在案例中把Clang插桩相关代码封装在一个文件中,方便后续使用。

温馨提示:获取到启动过程的全部符号后,就关掉SanitizerCoverage,并删除OrderFileTool工具类,若以后App启动相关业务发生变更后,再重新排列一次就可以了。

推荐阅读

1. iOS原理 App的启动优化1:优化建议
2. 抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15%
3. Clang插桩覆盖的官方文档
4. iOS调优 | 深入理解Link Map File

上一篇下一篇

猜你喜欢

热点阅读