美文网首页
iOS启动时间优化:二进制重排的实践

iOS启动时间优化:二进制重排的实践

作者: 搬砖人666 | 来源:发表于2020-11-13 12:11 被阅读0次

实现思路:

  • 方法打桩,记录启动时调用的方法,
  • 按时间排序,生成order文件。
方法打桩:

编写脚本通过正则表达式,匹配Object-C方法,在每一个方法中都插入记录代码,配合dispatch_once,一次启动即可记录到目标时间点所有执行的方法。
脚本核心代码(python实现):
下边是oc方法的正则插入记录代码的脚本,还需要考虑oc工程中不少的c方法,Swift也可以如法炮制。

def change(value):
    global current_string
    line = value.group()
    if '\\' in line:
        return line
    temp = line + ' \\'
    if temp in current_string:
        return line
    temp2 = line + '\\'
    if temp2 in current_string:
        return line
    methods_record_code = "    // =========方法打桩 Start =======\n\
    static dispatch_once_t onceTokenRecordMethods;\n\
    dispatch_once(&onceTokenRecordMethods, ^{\n\
        NSMutableArray *array_ccc = [[NSClassFromString(@\"GCTRecordMethods\") performSelector:NSSelectorFromString(@\"new\")] valueForKey:@\"array\"];\n\
        NSDictionary *dictionary_ccc = @{@\"name\":[NSString stringWithFormat:@\"%s\", __FUNCTION__],@\"time\":[NSDate date],@\"sel\":NSStringFromSelector(_cmd)};\n\
        [array_ccc addObject:dictionary_ccc];\n\
    });\n\
    // =========方法打桩 End =======\n"
    return line + '\n' + methods_record_code

current_string  = file_object.read()//读取目标类型文件
re.sub(r"\n(\-|\+)[\s]*([(][\s]*(.*)[\s]*[*]?[\s]*[)])[\s]*[^{;]+[\s]*?{", change,
                                current_string)
current_string  = file_object.write()//正则替换后再写入
如何记录

项目中添加辅助类,实现一个单例,把辅助类放在compile source的第一个,保证程序启动时辅助类在其他类之前加载,启动打桩的标记存入辅助类单例的属性数组中,然后在目标时间点(比如首页的viewDidAppear),取出数组中的数据,按时间排序后生成字符串写入本地txt文件,通过xcode拉出手机中的txt文件即可获得原始数据。

NSArray *array = [[[NSClassFromString(@"GCTRecordMethods") performSelector:NSSelectorFromString(@"new")] valueForKey:@"array"] mutableCopy];
    [array sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *  _Nonnull obj1, NSDictionary * _Nonnull obj2) {
        NSDate *time1 = obj1[@"time"];
        NSDate *time2 = obj2[@"time"];
        return [time1 timeIntervalSinceDate:time2] > 0;
    }];
    NSDateFormatter *dateFmt = [[NSDateFormatter alloc]init];
    dateFmt.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    NSMutableString *mstring = [[NSMutableString alloc] init];
    for (NSDictionary *dictionary in array) {
        NSString *name = dictionary[@"name"];
        name = [name stringByReplacingOccurrencesOfString:@"_block_invoke" withString:@""];
        [mstring appendString:name];
        [mstring appendString:@"\n"];
    }
    [self writeToTXTFileWithString:mstring fileName:@"iLifeMethod"];
获取到的txt文件示例:

+[AppDelegate(Debug) load]
+[GCTTest1 test]
+[GCTTest2 test]
+[GCTTest3 test]
_instanceMethodDefaultSwizzling
__alias_of_selector
_instanceMethodSwizzling
+[CommandQueue(Auth) load]
+[XXXViewController(Extension) load]

改名xx.order,在xcode中order file配置order文件的相对路径即可。

如何确认:

打开xcode配置项linkmap,配置order文件前和配置order文件后编译生成的linkmap比对即可,贴两张图:


重排前的linkmap文件symbols段 重排后的linkmap文件symbols段,和order文件顺序一致
验证重排效果

Instrument的App Launch测试,筛选主线程,选择virtual memory,下图是未设置order文件的测试结果,[AppDelegate load]方法中三个测试方法导致了三次缺页中断,设置order文件后,再次测试找到[AppDelegate load]File Backed Page In执行堆栈,三个方法没有了,对就是不见了,说明这三个方法没有再导致缺页中断,这三个测试方法和[AppDelegate load]被加载进同一片4K的内存,[AppDelegate load]方法执行时间减少了523.83us,假设App启动一共执行了1000个方法,二进制重排总减少启动时长就是523830us=523.830ms=0.523830s,当然这些都是理论值,一般App的总启动时间也不会超过500ms。

测试方法1导致的缺页中断 测试方法2导致的缺页中断 测试方法3导致的缺页中断

废话

项目越大,启动执行方法越多,提升越明显。

后续更新

具体的总体提升数据等待完善后再更新。

相关理论参考下边这些文章

《iOS 优化篇 - 启动优化之Clang插桩实现二进制重排》
《简谈二进制重排》
《抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15%》
《iOS App启动时间优化 二进制重排和PGO》

相关文章

网友评论

      本文标题:iOS启动时间优化:二进制重排的实践

      本文链接:https://www.haomeiwen.com/subject/mmsggctx.html