关于启动优化需要知道的一些事

关于启动优化需要知道的一些事

1. 时间构成

  • T1 点击 APP 到执行 main 函数.

  • T2 从 main 函数开始到 appDelegatedidFinishLaunchingWithOptions

  • T3 执行完 didFinishLaunchingWithOptions 后App还需要做一些初始化工作,然后经历定位、首页请求、首页渲染等过程后,用户才能真正看到数据内容并开始使用,我们认为这个时候冷启动才算完成。这个过程定义为 T3

冷启动时间: T = T1 + T2 + T3 启动优化在这三个阶段都可以施展拳脚.

1.1. 影响因素:

T1 阶段

  • 执行了大量的 load() 方法
  • 加载了无用的类及方法

T2 阶段

  • 执行了大量的启动项任务
  • 同步 I/0 操作
  • 一些隐晦的耗时操作

T2 阶段

  • 定位,请求,渲染

1.2. 发展历程

一般在 APP 早起开发阶段冷启动不会有明显的问题, 冷启动问题也不是在某个版本出现的,是长期一个量变到质变的过程。

而是随着版本迭代,App功能越来越复杂,启动任务越来越多,冷启动时间也一点点延长。最后注意到并想要优化它的时候,这个问题已经变得很棘手了。

2. 优化思路及方向

问题思考方方向
1. 解决存量问题优化当前性能瓶颈点,优化启动流程,缩短冷启动时间。
2. 管控增量问题冷启动流程规范化,通过代码范式和文档指导后续冷启动过程代码的维护,控制时间增量
3. 完善监控策略完善冷启动性能指标监控,收集更详细的数据,及时发现性能问题

2.1. T1 的时间过程有哪些?

T1 也就是 main() 函数之前

main()之前操作系统所做的工作就是把可执行文件(Mach-O格式)加载到内存空间,然后加载动态链接库 dyld,再执行一系列动态链接操作和初始化操作的过程(加载、绑定、及初始化方法。

如何优化:

  • staticlib

  • 二进制重排

查看时间:

添加

值为 1.

DYLD_PRINT_STATISTICS

Total pre-main time: 1.1 seconds (100.0%)
         dylib loading time: 729.70 milliseconds (63.6%)
        rebase/binding time:  65.69 milliseconds (5.7%)
            ObjC setup time: 155.19 milliseconds (13.5%)
           initializer time: 196.28 milliseconds (17.1%)
           slowest intializers :
             libSystem.B.dylib :   6.89 milliseconds (0.6%)
    libMainThreadChecker.dylib :  49.65 milliseconds (4.3%)
                   MenuItemKit :  87.14 milliseconds (7.5%)

具体在 main 函数之前做的是:

真正的加载过程从 exec() 函数开始,exec() 是一个系统调用。操作系统首先为进程分配一段内存空间,然后把可执行文件 Mach-O格式加载到内存,接着加载 Dyld(动态链接器), 再接着由动态链接器加载动态库,

2.1.1. exec()

真正的加载过程从exec()函数开始,exec()是一个系统调用。操作系统首先为进程分配一段内存空间。

2.1.2. 加载 Mach-O 格式的可执行文件

组成成分
HeaderHeader中包含的是可执行文件的CPU架构,Load Commands的数量和占用空间
Load CommandsLoad Commands中包含的是Segment的Header与内存分布,以及依赖动态库的版本和Path等
Segment DataSegment Data就是Segment汇编代码的实现,每段Segment的内存占用大小都是分页页数的整数倍

2.1.3. 加载 dyld(动态链接器)

2.1.4. dyld 加载动态库 dylib

dyld读取完 Mach-O的Header和Load Commands后,就会找到可执行文件的依赖动态库。接着dyld会将所依赖的动态库加载到内存中。这是一个递归的过程,依赖的动态库可能还会依赖别的动态库,所以dyld会递归每个动态库,直至所有的依赖库都被加载完毕。

加载后的动态库会被缓存到dyld shared cache中,提高读取效率。

2.1.5. Rebase & Bind

  • Rebase在Image内部调整指针的指向。把动态库加载到指定地址,由于地址空间布局是随机的,需要在原来的地址根据随机的偏移量做一下修正 (可以理解为地址翻译)
  • Bind是把指针正确地指向Image外部的内容。这些指向外部的指针被符号(symbol)名称绑定,dyld需要去符号表里查找,找到symbol对应的实现

2.1.6. Objc setup

  • 注册Objc类 (class registration)
  • 把category的定义插入方法列表 (category registration)
  • 保证每一个selector唯一 (selector uniquing)

因为Objective C的动态特性,所以在Main函数执行之前,需要把类信息注册到一个全局Table中。同时,Category的方法也会被注册到对应类中,Category中的同名方法实现,会根据编译顺序,被最后一个编译的Category实现所覆盖。同时还会做Selector的唯一性检测。

2.1.7. Initializers

  • Objc的+load()函数
  • C++的构造函数属性函数
  • 非基本类型的C++静态全局变量的创建(通常是类或结构体)

这里区分下+load方法与+Initialize方法,前者是在类加载时调用的,后者是在类第一次收到message之前调用的。

Avatar
尹玉广
iOS开发者

相关