iOS 启动优化(一) pre-main

如何精确度量 iOS App 的启动时间

iOS启动分为两个时间:

  1. pre-main时间
  2. main时间

一、pre-main时间检测

Xcode 提供了一个很赞的方法,只需要在 Edit scheme -> Run -> Arguments 中将环境变量 DYLD_PRINT_STATISTICS 设为 1,就可以看到 main 之前各个阶段的时间消耗

Total pre-main time: 341.32 milliseconds (100.0%)
         dylib loading time: 154.88 milliseconds (45.3%)
        rebase/binding time:  37.20 milliseconds (10.8%)
            ObjC setup time:  52.62 milliseconds (15.4%)
           initializer time:  96.50 milliseconds (28.2%)
           slowest intializers :
               libSystem.dylib :   4.07 milliseconds (1.1%)
    libMainThreadChecker.dylib :  30.75 milliseconds (9.0%)
                  AFNetworking :  19.08 milliseconds (5.5%)
                        LDXLog :  10.06 milliseconds (2.9%)
                        Bigger :   7.05 milliseconds (2.0%)

还有一个方法获取更详细的时间,只需将环境变量 DYLD_PRINT_STATISTICS_DETAILS 设为 1 就可以。

  total time: 2.8 seconds (100.0%)
  total images loaded:  488 (471 from dyld shared cache)
  total segments mapped: 61, into 24958 pages
  total images loading time: 1.1 seconds (40.6%)
  total load time in ObjC:  92.39 milliseconds (3.2%)
  total debugger pause time: 794.39 milliseconds (28.2%)
  total dtrace DOF registration time:   0.00 milliseconds (0.0%)
  total rebase fixups:  921,005
  total rebase fixups time: 109.77 milliseconds (3.9%)
  total binding fixups: 694,265
  total binding fixups time: 766.41 milliseconds (27.2%)
  total weak binding fixups time:   9.05 milliseconds (0.3%)
  total redo shared cached bindings time: 768.13 milliseconds (27.3%)
  total bindings lazily fixed up: 0 of 0
  total time in initializers and ObjC +load: 690.73 milliseconds (24.5%)
                         libSystem.B.dylib :  11.67 milliseconds (0.4%)
               libBacktraceRecording.dylib :  12.06 milliseconds (0.4%)
                           libobjc.A.dylib :   6.09 milliseconds (0.2%)
                libMainThreadChecker.dylib :  59.50 milliseconds (2.1%)
              libViewDebuggerSupport.dylib :   7.66 milliseconds (0.2%)
                      libglInterpose.dylib : 286.97 milliseconds (10.2%)
                       libMTLCapture.dylib :   4.28 milliseconds (0.1%)
                          AWUnityFramework : 103.15 milliseconds (3.6%)
                           AiWayFashionCar : 365.65 milliseconds (12.9%)
total symbol trie searches:    1594338
total symbol table binary searches:    0
total images defining weak symbols:  63
total images using weak symbols:  133

1. 优化(dylib loading time):

在项目优化实践中,我们移除了一个没有必要的动态库,并将几个动态库合成为一个动态库,减少动态库数量

第一个阶段:pre-main time 中第一个阶段 dylib loading time : 动态库加载阶段

***注: ***
如何查看动态库的个数: Products 中.app中会有Frameworks文件夹 里面即App需要引入的动态库

举例:Flutter优化日志

这里区分两种方式加载Flutter:

区别:
1. 第一种方式会将flutter依赖的第三方插件做成pod子仓的形式直接引入的源码,
App.framework Flutter.framework
2. 第二种方式会将flutter依赖的第三方插件做成framework,之后将所有的framework做成pod仓库
App.framework, FlutterPluginRegistrant.framework ,shared_preferences.framework, wakelock.framework , FMDB.framework , flutter_boost.framework , sqflite.framework , webview_flutter.framework, Flutter.framework , path_provider.framework , video_player.framework

造成的后果是: 第二种主工程会引入很多framework,造成的影响是动态库加载时间变长

第一种:采取直接污染主工程方式: 由于日志较多,仅展示敏感数据

Total pre-main time: 685.23 milliseconds (100.0%)
        dylib loading time: 152.81 milliseconds (22.3%)
      rebase/binding time:  74.06 milliseconds (10.8%)
         ObjC setup time:  44.86 milliseconds (6.5%)
        initializer time: 413.48 milliseconds (60.3%)
Total pre-main time: 980.53 milliseconds (100.0%)
        dylib loading time: 163.32 milliseconds (16.6%)
       rebase/binding time:  86.59 milliseconds (8.8%)
           ObjC setup time: 223.50 milliseconds (22.7%)
          initializer time: 507.11 milliseconds (51.7%)

第二种:采取pod仓库形式

Total pre-main time: 818.21 milliseconds (100.0%)
        dylib loading time: 243.54 milliseconds (29.7%)
       rebase/binding time:  72.09 milliseconds (8.8%)
           ObjC setup time:  39.28 milliseconds (4.8%)
          initializer time: 463.27 milliseconds (56.6%)
Total pre-main time: 1.3 seconds (100.0%)
        dylib loading time: 270.75 milliseconds (20.3%)
       rebase/binding time:  69.74 milliseconds (5.2%)
           ObjC setup time: 282.78 milliseconds (21.2%)
          initializer time: 708.54 milliseconds (53.2%)

对比结果: dylib loading time 时间拉长了100多毫秒

技术选择:

  • 第一种方式污染主工程: 可以看到结果是framework变少
  • 第二种方式极少的污染主工程: 结果是framework变多

2. 优化(rebase/binding time):

这一阶段系统主要注册 Objc 类。所以,指针数量越少越好。这一步能做的优化有:

  • 清理项目中无用的类
  • 删减没有被调用到或者已经废弃的方法
  • 删减一些无用的静态变量

核心思想是在进行动态库的重定位和绑定(Rebase/binding)(ASLR:dylib会被加载到随机地址,这个随机的地址跟代码和数据指向的旧地址会有偏差,dyld 需要修正这个偏差,做法就是将 dylib 内部的指针地址都加上这个偏移量) 过程中减少指针修正;
减少Objective-C类数量,减少分类,减少实例变量和函数(删除不用的类以及冗余代码,再深一点就是减少第三方工具的使用,可以查看源码,自己实现);
减少C++虚函数;

3.优化(ObjC setup time):

Objc Setup Time
这一步主要做了以下操作
注册Objc类 (class registration)
把category的定义插入方法列表 (category registration)
保证每一个selector唯一 (selctor uniquing)
前两部做好之后这一步就没有什么可以有优化的

4. 优化(initializer time):

第一个阶段:pre-main time 中第4个阶段 initializer time :

这一阶段 dyld开始运行程序的初始化函数,调用每个Obj类和分类的+load方法,
这一阶段,dyld 开始运行程序的初始化函数,调用每个 Objc 类和分类的 +load 方法,调用 C/C++ 中的构造器函数。initializer阶段执行完后,dyld 开始调用 main() 函数。在这一步,检查 +load 方法,尽量把事情推迟到 +initiailize 方法里执行。

  • 使用initialize替代load方法
  • 减少使用c/c++的attribute((constructor));推荐使用dispatch_once(),peathrd_once(), std:once()等方法
  • 不要在初始化中创建线程
  • 推荐使用swift
优化前
Total pre-main time: 731.83 milliseconds (100.0%)
         dylib loading time: 250.62 milliseconds (34.2%)
        rebase/binding time:  50.32 milliseconds (6.8%)
            ObjC setup time:  37.81 milliseconds (5.1%)
           initializer time: 393.07 milliseconds (53.7%)
           slowest intializers :
             libSystem.B.dylib :   6.20 milliseconds (0.8%)
    libMainThreadChecker.dylib :  28.41 milliseconds (3.8%)
          libglInterpose.dylib : 187.20 milliseconds (25.5%)
               AiWayFashionCar : 244.98 milliseconds (33.4%)
优化后
Total pre-main time: 667.68 milliseconds (100.0%)
         dylib loading time: 237.69 milliseconds (35.6%)
        rebase/binding time:  53.64 milliseconds (8.0%)
            ObjC setup time:  36.47 milliseconds (5.4%)
           initializer time: 339.86 milliseconds (50.9%)
           slowest intializers :
             libSystem.B.dylib :   6.05 milliseconds (0.9%)
    libMainThreadChecker.dylib :  28.70 milliseconds (4.2%)
          libglInterpose.dylib : 152.15 milliseconds (22.7%)
               AiWayFashionCar : 218.33 milliseconds (32.7%)

// 经全局查找看到有一个load方法里面执行了IO操作相关API, 优化之后 initializer time 有所优化

二、main 之后的时间度量

main 到 didFinishLaunching 结束或者第一个 ViewController 的viewDidAppear 都是作为 main 之后启动时间的一个度量指标。

这个时间统计直接打点计算就可以,不过当遇到时间较长需要排查问题时,只统计两个点的时间其实不方便排查,目前见到比较好用的方式就是为把启动任务规范化、粒子化,针对每个任务都有打点统计,这样方便后期问题的定位和优化。

以此记录优化启动日志

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

推荐阅读更多精彩内容