iOS二进制重排对缺页和启动时间的优化效果研究

零. 前言

头条团队去年编写的基于二进制文件重排的解决方案,为APP启动速度提升了超过15%,引起了各路大神的兴趣,业界也多了几篇优质的二进制重排的文章,下面我将会尝试用这些文章的方法实践一下,看看效果,也研究一下能不能应用于自己的工程内。

一. 有关Order File

在一份说明文档中,他的描述是这样的,大概意思就是将生成的order file的路径配置到 Xcode 的 Build Settings 中的 Order File 配置项,随后链接器就会按照 order file 中的顺序来排列符号了。


The path to a file which alters the order in which functions and data are laid out. For each section in the output file, any symbol in that section that are specified in the order file is moved to the start of its section and laid out in the same order as in the order file. Order files are text files with one symbol name per line. Lines starting with a # are comments. A symbol name may be optionally preceded with its object file leafname and a colon (e.g. foo.o:_foo). This is useful for static functions/data that occur in multiple files. A symbol name may also be optionally preceded with the architecture (e.g. ppc:_foo or ppc:foo.o:_foo). This enables you to have one order file that works for multiple architectures. Literal c-strings may be ordered by quoting the string in the order file (e.g. "Hello, world"). Generally you should not specify an order file in Debug or Development configurations, as this will make the linked binary less readable to the debugger. Use them only in Release or Deployment configurations.

换言之,order file的意义就是让函数方法进行重排,以尽量避免缺页Page Fault的次数,从而降低启动过程中缺页造成的耗时。

二. 怎么看效果

看效果主要从几个方面来考察:

  • 重排成功了没 - 看LinkMap
  • 重排效果怎么样 - 看Page Fault次数
  • 对于工程的优化效果 - 看启动时间

1. LinkMap

LinkMap的生成方法和结构在我之前写过的LinkMap初探已经提到过,这里简单提一下。

order file在iOS上只支持__text代码段的重排,而对于其余section,如__cstring,__ustring,__const,_objc等都是不支持重排的,所以如果我们想看重排在LinkMap的表现,只需要看# Symbols下的排列方法有没有按照我们的Order File来排序就可以了。

2. Page Fault

其实在头条那篇技术博客也有提到了,就是用InstrumentSystem Trace来看,再结合os_signpost来定位到启动前的时间段,即可看到Page Fault的次数。

os_signpost可以用来打点,我们只需要在停止收集order file的那个方法里面打个点,在System Trace上面加个os_signpost方法,就可以知道order file对Page Fault的效果了。

最后看项目主线程的File Backed Page In,即为缺页次数

更详细的os_signpost教程在这里

3. 启动时间

XCode里面的Edit Scheme,添加一个DYLD_PRINT_STATISTICS,对应Value为1即可看到。在Pre-main中,可以大致分为load dylib->rebase->bind->Objc setup-> initializer,开发能掌握和度量的是initializer部分。

每个时间点的具体分析在这里

三. 各种方法尝试

简谈二进制重排提到,trace的方式有两种:编译插桩和运行时插桩,他们各有优缺点。

1. 编译插桩

编译插桩采用的是腾讯大神杨帝的方法,这个方法的思路是利用了SanitizerCoverage收集到了工程内所有方法,在MainVC的ViewDidAppear方法停止收集(停止收集的时机可以自定义),即可获取到主VC显示前调用的所有方法,从而生成Order File

extern void AppOrderFiles(void(^completion)(NSString *orderFilePath)) {
    // 停止收集
    collectFinished = YES;
    __sync_synchronize();
    // 排除当前函数名
    NSString *functionExclude = [NSString stringWithFormat:@"_%s", __FUNCTION__];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSMutableArray <NSString *> *functions = [NSMutableArray array];
        while (YES) {
            PCNode *node = OSAtomicDequeue(&queue, offsetof(PCNode, next));
            if (node == NULL) {
                break;
            }
            Dl_info info = {0};
            dladdr(node->pc, &info);
            if (info.dli_sname) {
                NSString *name = @(info.dli_sname);
                BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
                NSString *symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
                [functions addObject:symbolName];
            }
        }
        if (functions.count == 0) {
            if (completion) {
                completion(nil);
            }
            return;
        }
        // 获取到所有的方法,开始遍历
        NSMutableArray<NSString *> *calls = [NSMutableArray arrayWithCapacity:functions.count];
        NSEnumerator *enumerator = [functions reverseObjectEnumerator];
        NSString *obj;
        while (obj = [enumerator nextObject]) {
            if (![calls containsObject:obj]) {
                [calls addObject:obj];
            }
        }
        [calls removeObject:functionExclude];
        NSString *result = [calls componentsJoinedByString:@"\n"];
        // 写入文档
        NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"app.order"];
        NSData *fileContents = [result dataUsingEncoding:NSUTF8StringEncoding];
        BOOL success = [[NSFileManager defaultManager] createFileAtPath:filePath
                                                               contents:fileContents
                                                             attributes:nil];
        if (completion) {
            completion(success ? filePath : nil);
        }
    });

对于LinkMap,Order File的确让LinkMap的顺序发生了改变,也说明设置OrderFile是有效的,他会按照生成的app.order的顺序重排了一遍。

2. 运行时插桩

运行时插桩用的则是rhythmkay大佬的方案PGOAnalyzer,不过由于项目并不开源,所以只能按照GitHub说明直接导入Framework来运行,大概原理是通过hook或动态插桩来记录,所以一些.a的函数也可以被拿出来。

导出来的OrderFile行数是编译插桩的6倍,而且确确实实有许多Pod的函数被导出来了,缺页次数和ReOrder差不多,启动时间则有点不稳定,总Pre-main时间时而比之前的长,时而比之前的短,且主工程启动时间较长。

四. 效果对比

为了让效果看上去更明显,我特地拿了一部祖传的、卡的一批的小6,来看看没有OrderFile、使用原代码的生成OrderFile、改良后的OrderFile(我命名为ReOrder)的效果,每个情况运行七次查看缺页次数:

1. 对于Page Fault:

没有Order File的缺页次数
3882 505 661 618 294 426 300

平均缺页:955.142857143
中位数:505

编译插桩的缺页次数
492 303 299 356 769 735 1003
平均缺页:565.285714286
中位数:492

运行时插桩的缺页次数
563 323 1387 2152 296 1382 1521
平均缺页:1089.14285714
中位数:1382

虽然没有设置OrderFile的平均次数看上去高一点,但只不过是第一次偶然的3000+的缺页拖高了平均数,其他数据看上去比较正常..相对来说,有没有OrderFile看上去对缺页影响也不大。

2. 对于启动时间:

没有OrderFile
编译插桩
运行时插桩

启动时间这个东西有点玄学,即时是加了OrderFile也有可能冲上1.5s,没加OrderFile也有可能少于1s,感觉耗时偏差过大,也不好作对比,反正我是没看出什么明显的效果来。

3. 手动测一下首屏时间:

好吧,自己再弄多一个APP,计时一下两个APP从开启到显示首屏的时间,为了看得出点差距,我把手机里面的游戏全部开了一遍,再打开这两个APP,但经过秒表计时,发现两个APP的首屏时间其实还是不相上下,没有看出明显的效果。

我不禁怀疑自己是不是设置OrderFile的姿势不对导致效果不明显,毕竟其他技术团队都宣称OrderFile有着非常显著的重排效果,疑惑之下看到了杨帝下GitHub也有人遇到效果不明显的疑惑,和杨帝的回复:

好吧,既然杨帝也这样说了,那这次的二进制重排探索先告一段落了,之后再看看有没有什么有利于工程的优化点可做了= =

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容