第十七篇:iOS启动优化

APP启动分为pre-main和mian两个过程,首先我们需要进行如下图设置,设置DYLD_PRINT_STATISTICS为1,这样我们就可以打印出来APP在启动的时候所花费的时间。


WechatIMG2107.jpeg

下面是各个阶段的消耗时间:

1. dylib loading time

这个时间优化,也就是dylib链接动态库的优化,可以减少动态库个数(指自己写的库),苹果给我们建议是我们自己写的动态库不要超过6个是最好的。

2.ObjC setup time

这个是注册objc所有类所需时间,在mach-0里读取所有文件的类,把它们会放到一张表里面。我们可以安装一个fui,通过终端输入 fui find命令可以打印出没有被使用的类,我们把这些没使用的类进行一些删除。还可以通过工具检测是否工厂里有没有使用的图片。

3. rebase/binding time

binding就是一个符号绑定,其会把一个函数具体实现地址和对应函数符号进行链接。比如printf函数打印,其内部是通过一个binging链接到printf具体函数实现和printf这个符号,然后进行打印。

rebase是一个进行指针的修复,这里有个虚拟内存的概念,在早期时候都是通过物理内存,这样会出现内存不够用,不安全。但是虚拟内存也会不安全,因为其地址都是从0开始,为了解决这个问题用到了ASLR(地址空间随机话),

4. initializer time

初始化时间优化,一边我们再load里不要添加耗时操作,因为load加载会在mian函数之前。
Total pre-main time: 2.0 seconds (100.0%)
         dylib loading time: 232.43 milliseconds (11.4%)
        rebase/binding time: 1.0 seconds (53.4%)
            ObjC setup time: 224.46 milliseconds (11.0%)
           initializer time: 486.86 milliseconds (23.9%)
           slowest intializers :
             libSystem.B.dylib :  61.13 milliseconds (3.0%)
             libprotobuf.dylib :  55.00 milliseconds (2.7%)
                          Test : 185.69 milliseconds (9.1%)

二进制重排

每个page fault消耗时间虽然很少,但是多个时间就会很多。所以这个是我们优化的重点。App在执⾏的过程中会存在⼤量的Page Fault,⼀个Page Fault
的耗时很少,但是当⼤量的Page Fault存在时,就会影响到代码的执⾏速度。同理,在App启动的时候,就可能会出现⼤量的Page Fault。⼆进制重排就是把在启动过程中需要使⽤到的符号,重新排列在⼀个或者⼏个Page⾥⾯,减少Page Fault的次数,从⽽达到减少启动时间的⽬的。

在build phrase里我们可以重新排列调用文件顺序,但是这样不是一个好办法。


WechatIMG2108.jpeg

可以通过设置.order文件,因为系统在启动的时候是会先读.order文件进行配置启动。首先通过终端创建一个lg.order文件。然后我们可以看到就是按我们.order文件进行排序的。


WechatIMG2110.jpeg
WechatIMG2109.jpeg

那我们如何知道哪些函数在启动的时候使用需要放到前面,这个就需要clang插桩技术,首先我们开启clang插桩,然后需要添加几个头文件#include <stdint.h>,#include <stdio.h>,#include <sanitizer/coverage_interface.h>。

WechatIMG2111.jpeg

在viewController里也添加了如下的两个方法:


void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                         uint32_t *stop) {
    static uint64_t N;
    if (start == stop || *start) return;
    printf("INIT: %p %p\n", start, stop);
    for (uint32_t *x = start; x < stop; x++)
        *x = ++N;
    NSLog(@"%d",N);
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//    NSLog(@"%s",__func__);
    if (!*guard) return;
    void *PC = __builtin_return_address(0);
    
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,0};
    
    //插入结构体
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, abc));
    
//    printf("%s\n",info.dli_sname);
}

上面运行后会打印如下,也就是stop和start的打印,下面30代表是的30个方法。下面是4个字节的存储01 00 00 00,其存的值就是1,2,3,4,5,6,7,8。通过

1.__sanitizer_cov_trace_pc_guard_init这个方法也就知道了在项目中用到的符号(方法)的个数。

2.系统每调用一个方法,都会回调这个void __sanitizer_cov_trace_pc_guard(uint32_t *guard) 这个方法,我们发现这个方法会到各个方法调用之前会被调用,那么就可以在这里获取到启动时候调用了哪些方法。

INIT: 0x1009e1750 0x1009e17c8
(lldb) x 0x1009e1750
0x1009e1750: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00  ................
0x1009e1760: 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00  ................
(lldb) x 0x1009e17c8
0x1009e17c8: 00 00 00 00 00 00 00 00 1e 00 00 00 00 00 00 00  ................
0x1009e17d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
2022-06-13 16:18:47.368235+0800 TraceDemo[43903:514812] 30

下面是获取一个函数的名称以及地址:
Dl_info info;
dladdr(node->pc, &info);
NSString *name = @(info.dli_sname);

typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object */
        void            *dli_fbase;     /* Base address of shared object */
        const char      *dli_sname;     /* Name of nearest symbol */
        void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;

下面dli_fbase是文件地址,dli_sname是名字也就是符号,dli_saddr符号的地址。其实我们只需要dli_sname就可以。


WechatIMG2116.jpeg

下面是dli_sname的打印,也就是启动后的程序所运行的函数,其中对于swift也有效,s9TraceDemo9SwiftTestC05swiftD0yyFZTo这些方法就是swift的,因为swift有自带混淆。

-[AppDelegate application:didFinishLaunchingWithOptions:]
-[SceneDelegate window]
-[SceneDelegate setWindow:]
-[SceneDelegate scene:willConnectToSession:options:]
-[ViewController viewDidLoad]
_$s9TraceDemo9SwiftTestC05swiftD0yyFZTo
_$s9TraceDemo9SwiftTestC05swiftD0yyFZ
_$ss27_finalizeUninitializedArrayySayxGABnlF
_$sSa12_endMutationyyF
_$ss5print_9separator10terminatoryypd_S2StFfA0_
_$ss5print_9separator10terminatoryypd_S2StFfA1_
-[SceneDelegate sceneWillEnterForeground:]
-[SceneDelegate sceneDidBecomeActive:]
-[ViewController touchesBegan:withEvent:]

通过如下操作可以进行二进制重排:可以直接生成.oder文件,这里是把二进制符号文件存储在symbolList里符号列表里。

//原子队列 线程安全 先进后出 (/*只能保存结构体*/不用讲)
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

typedef struct {
    void * pc;
    int abc;
} SYNode;

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                         uint32_t *stop) {
    static uint64_t N;
    if (start == stop || *start) return;
    printf("INIT: %p %p\n", start, stop);
    for (uint32_t *x = start; x < stop; x++)
        *x = ++N;
    NSLog(@"%d",N);
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    if (!*guard) return;
    void *PC = __builtin_return_address(0);
    
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,0};
    
    //插入结构体
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, abc));
    }

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //创建数组
    NSMutableArray *symbleNames = [NSMutableArray array];
    while (YES) {
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, abc));//取出symbolList里的方法符号
        if (node == NULL) {
            break;;
        }
        Dl_info info;
        dladdr(node->pc, &info);
        //转为OC字符串方便操作
        NSString *name = @(info.dli_sname);
        //OC方法 直接添加到数组
        BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
        NSString * symbleName = isObjc ? name : [@"_" stringByAppendingString:name];
        [symbleNames addObject:symbleName];
    }
    NSEnumerator *em = [symbleNames reverseObjectEnumerator];//去重
    //新数组存储
    NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbleNames.count];
    NSString *name;
    while (name = [em nextObject]) {
        if (![funcs containsObject:name]) {
            [funcs addObject:name];
        }
    }
    
   //生成.order文件
    //数组转为字符串
    NSString *funcStr = [funcs componentsJoinedByString:@"\n"];
    //文件路径
    NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"lg.order"];
    //文件内容
    NSData *file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    //写入文件
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
    //获取沙盒主目录路径
    NSLog(@"%@",NSHomeDirectory());
    
    NSLog(@"%@",funcStr);
}

二进制重排总结

  1. 在二进制重排里我们首先需要进行设置:


    WechatIMG2121.jpeg

2.然后其会调用__sanitizer_cov_trace_pc_guard_init(这里可以打印出启动调用了几个方法) 和void __sanitizer_cov_trace_pc_guard(这里就是启动时候每调用一个方法就会回调这个)这两个方法,然后生成.order文件。

3.在生成了.order文件之后,需要进行配置处理,放了之后的话编译器就会按照.order的顺序进行排列了。


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

推荐阅读更多精彩内容