美文网首页IOS性能优化
iOS 启动优化(一) pre-main

iOS 启动优化(一) pre-main

作者: kalpa_shock | 来源:发表于2022-06-09 10:42 被阅读0次

    如何精确度量 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

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