冷启动优化

2020-05-11  本文已影响0人  哦小小树

App启动分为冷启动和热启动,我们说的启动优化一般是指冷启动优化。若要想优化,首先我们必须明确启动过程。

启动过程分为两个阶段

main函数前

可以通过添加Xcode环境变量DYLD_PRINT_STATISTICS来打印pre-main花费的时间

如果想查看更加详细的信息再添加环境变量DYLD_PRINT_STATISTICS_DETAILS

Total pre-main time: 5.0 seconds (100.0%)
         dylib loading time: 3.6 seconds (72.2%)
        rebase/binding time: 1.1 seconds (22.9%)
            ObjC setup time: 100.24 milliseconds (1.9%)
           initializer time: 139.23 milliseconds (2.7%)
           slowest intializers :
             libSystem.B.dylib :   5.24 milliseconds (0.1%)

执行流程

启动优化.png
  1. 加载可执行文件
  2. 加载动态库由于使用了ASLRAdress Space Layout Randomizationrebase[调整镜像内指针偏移] binding[调整对镜像外符号信用偏移];一般来说动态库会加载100-400个大多数为OS dylib(系统动态库),相对来说OS dylib在加载时做了优化调整
  3. 初始化runtime:包括,类注册,属性动态调整,方法唯一性验证,分类中方法插入到类方法中
  4. C++静态变量初始化,构造函数attribute(constructor)初始化

优化方案

  1. 减少动态库加载:苹果支持6个非系统动态库合并
合并动态库:
lipo -create path/yourFramework1 path/yourFramework2 -output path/yourFramework

合并静态库:
lipo -create '/sim/lib.a' '/dev/lib.a' -output 'lib.a'
  1. 减少ObjC类(class),方法(selector),分类(category)的数量

    ObjC因为支持了动态语言,内部会维护一个记录类名与类关系的表,如果类数量多就会造成表很大

    • 移除不用的类
    // 安装fui工具,在终端中执行命令 官网:https://github.com/dblock/fui
    sudo gem install fui -n /usr/local/bin
    
    // 使用, 查找当前目录下未使用的类;注意查找出的内容并不一定靠谱,还需要自己在XCode中验证
    fui find
    
    // 使用otool命令可查看__DATA.__objc_classrefs段和__DATA.__objc_classlist段,两者的差集可以认为是定义了但未使用的类。
    
    // 1. 获取可执行文件下所有OC类名
    otool -v -s __DATA __objc_classlist 可执行文件名
    
    // 2. 获取可执行文件所有引用到的OC类名
    otool -v -s __DATA __objc_classrefs 可执行文件名
    
    // 3. 两者的差集就是代码中没有直接使用到的OC类,不过需要注意反射机制使用类名来操作的情况,需要人工做下筛选
    // 4. 获取所有符号,可以取出地址与符号对应关系
    nm -nm 可执行文件名
    
    // 5. 如果一个子类实例化,父类未实例化,那么父类不会出现在__objc_classrefs这个段里。需要将移除未使用的这部分OC类
    // 可以获取到类的继承关系
    otool -oV 可执行文件名
    
    参考资料:https://juejin.im/post/5d5d1a92e51d45620923886a
    参考脚本:https://github.com/xuezhulian/classunref
    
  1. load方法中的内容推迟到initialize中操作
放到initialize方法中,并适时使用dispatch_once来保证执行效果
  1. 减少C++虚函数的数量
创建虚函数表有开销;
  1. 减少C/C++中构造器,非基本类型的常量
C/C++的构造器函数(用attriubte((constructor))修饰的函数),和创建非基本类型的C++静态全局变量(通常是类或结构体)
  1. 使用Swift struct
内部做了优化,符号数量更少
  1. 二进制重排,减少page fault产生

请求分页系统中每当要访问的页面不存在是,便会触发一个缺页中断(page fault),然后操作系统就会阻塞这个进程,,直到调页完成后,才会重新执行。由此如果发生缺页中断次数太多就会耗时较多。我们此处只考虑启动过程中

缺页中断视频讲解
https://www.bilibili.com/video/av86534144?p=6

重排的目的就是为了在启动过程中尽可能少的触发page fault。

查看当前App启动产生多少次page fault.

1. 通过Profile -> System Trace
2. 选择真机,选择工程,点击启动,当首个页面加载出来就停止
3. 选择主线程,-> 选择 Summary:Virtual Memory 其中File Backed Page In  Count 即为pageFault次数

方案:

1. 构建一个orderFile.order;有序文件
2. 在Xcode Build settings中搜索Order File,配置orderfile路径

本质是通过全局的AOPHook所有方法

使用AppOrderFiles工具进行操作
https://github.com/yulingtianxia/AppOrderFiles

详细理解方案
http://www.zyiz.net/tech/detail-127196.html


main函数后

执行流程

  1. 首屏初始化所需配置文件的读写操作
  2. 首屏列表数据的读取
  3. 首屏渲染的大量计算等

优化方案

梳理功能调整调用时机

a. 可以延迟加载的库延迟
b. 复杂耗时计算,延迟或者开线程
c. 首页控制器最好使用纯代码方式

常用策略

分析link map文件

link map是编译连接时生成的一个txt文件,它生成的目的就是帮助程序员分析包大小。

link map记录了每个方法在当前二进制架构下占据的空间。通过分析link map,我们可以了解到每个类甚至每个方法占据了多少安装包空间。

使用方式如下

// 1. 开启
Xcode build setting 中开启Write Link Map

// 2. 配置Linkmap路径
path to link map

总结

大概阐述导致启动慢的原因,及解决方案;
针对比较深入的知识点,也查看了不少资料,作为切入理解的点。作为后续深入研究的方向。


参考及延伸:

https://www.jianshu.com/p/b19cd03eea68
http://yulingtianxia.com/blog/2019/09/01/App-Order-Files/
http://www.zyiz.net/tech/detail-127196.html
https://developer.apple.com/library/archive/documentation/Performance/Conceptual/CodeFootprint/Articles/ImprovingLocality.html#//apple_ref/doc/uid/20001862-117091-BCIBJEBH

上一篇 下一篇

猜你喜欢

热点阅读