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 之后启动时间的一个度量指标。

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

以此记录优化启动日志

你可能感兴趣的:(iOS 启动优化(一) pre-main)