美文网首页iOS 开发每天分享优质文章自鉴
APP启动过程详解+优化(二进制重排)

APP启动过程详解+优化(二进制重排)

作者: 冼同学 | 来源:发表于2021-02-04 16:43 被阅读0次

    APP的启动过程

    冷启动:APP在完全杀死的状态,需要系统新创建一个进程分配给它的情况。这是一次完整的启动过程。(二进制重排主要针对冷启动的优化)

    热启动:App 在冷启动后,用户将App 退到后台,即在App的进程还在系统里的情况下,用户重新启动进入 App 的过程,这个过程做的事情非常少,启动速度非常快。

    启动时间的组成

    启动时间得可以根据main()函数前后进行划分,大致分为以下时间段。

    1.t1阶段,main()之前的处理所需时间,称为pre-main。

    2.t2阶段,main()及main()之后处理所需时间。

    3.首屏渲染完成后。

    测量 pre-main 时间

    Edit scheme -> Run -> Auguments 添加环境变量 DYLD_PRINT_STATISTICS,value设为YES(图1)。

    图1

    然后启动项目,就可以看到各方面的时长(图2)。

    图2

    由图2可以知道  t1阶段:pre-main流程如下(图3):

    图3

    加载dylib:

    加载可执行文件(App 的.o 文件的集合),加载动态链接库。

    在dylib的加载过程中系统为了安全考虑引入了ASLR(Address Space Layout Randomization)技术和代码签名。

    ASLR技术:镜像Image、可执行文件、dylib、bundle在加载的时候会在其指向的地址(preferred_address)前面添加一个随机数偏差(slide),防止应用内部地址被定位。

    rebase/bind

    dylib加载完成之后,它们处于相互独立的状态,需要绑定起来。

    rebase:将镜像读入内存,修正镜像内部的指针,性能消耗主要在IO。(结合ASLR偏移量,计算好地址)。

    bind:查询符号表,进行方法绑定,修正镜像内部的指针。

    Objc Set Up

    Objc 运行时的初始化处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;

    initializer

    initializer:初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。

    总结:结果上面的分析,得到得优化方案有:

    1.减少动态库加载,每个动态库都有其的依赖关系。

    2.减少加载启动后不会去使用的类或者方法。

    3.+load方法的内容尽量在加载完成后执行,或使用 +initialize()方法替换掉,尽量进行懒加载。

    注意:在进行二进制重排之前我们还需要知道内存是怎么操作的。

    内存管理

    内存是分页管理的,映射表(虚拟内存)不是以字节为单位,是以页为单位的。macOS,Linux一页的大小为4k,iOS是一页大小为16k。

    物理内存

    早期的计算机没有虚拟内存这一说,系统的程序运行都会全部加载到内存中,这样子就会造成了大量资源的浪费。如果内存不够,程序将无法打开。必须先关闭前面的部分应用才能继续开启,这样子性能就会存在很大的问题。其次,因为程序放到了物理内存且内存地址是连续性的,别人通过其他运行中的软件访问到物理内存的话,就会跨程序访问到其他程序的物理内存,找到对应的点进行攻击,这里也是一个非常大的安全问题。如图4:

    图4

    虚拟内存

    针对物理内存的种种问题与弊端,工程师们就提出了虚拟内存,完美的解决了以上的问题。首先我们的程序加载时候会进行模块化(分页)处理,生成了一张 虚拟内存和物理内存关联的表 --映射表(地址为0 - 4G)。图5:

    图5

    当App需要使用某一块虚拟内存的地址时,会通过这张表查询该虚拟地址是否已经在物理内存中申请了空间。如果已经申请了则通过表的记录访问物理内存地址,如果没有申请则申请一块物理内存空间并记录在表中(Page)。这个通过进程映射表映射到不同的物理内存空间的操作叫地址翻译,这个过程需要CPU和操作系统配合。(注意:分页的首地址不是从映射表的0开始的,虚拟内存地址分配还会涉及到 ASLR)

    分页异常(Page Fault)

    在分页加载的过程中,当分内没有存在内存的时候,系统就会阻塞该进程,将磁盘中对应Page的数据加载到内存,把虚拟内存指向物理内存。在启动的过程中会存在大量得分页异常,就会重复大量的做阻塞和重载操作,那么为了解决这问题,就引入了二进制重排。

    补充:如果我们的内存加载满了,内存就会根据程序的活跃度来释放分页。平时我们放久的微信重新进去就会被移除内存,会出现冷启动的现象。

    二进制重排

    重排目的:二进制重排就是为了减少启动时因为分页异常而浪费的时间。

    没重排 已重排

    由上可知,二进制重排主要是启动时必须要加载的page放到前面优先加载,这样子就会减少分页缺失的发生,从而达到优化的效果。

    查看 Page Fault

    Instruments -> System Trace

    File Backed Page In 即为 Page Fault 的个数。

    链接映射文件 Link Map File

    Link Map File的配置和获取

    sections部分信息如下:

    由section信息可里可以看到启动时方法符号的排序。

    order_file的引入

    Xcode提供了排列符号的设置给开发者,设置 order_file 即可。objc 源码就采用了二进制重排优化。

    order_file的配置

    在根目录生成link.order文件,这里面就是方法符号的排序。

    Target -> Build Setting -> Linking -> Order File 设置 order file 的路径

    order file编辑

    order file的生成参考。

    注意:全手写一定是不可取的,想实现自动化就要解决下列问题:保证不遗漏方法,保证方法符号正确和保证方法符号顺序正确。

    解决方案可见 《抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15%》

    如果想了解编译期插桩的话请参考:https://juejin.cn/post/6844904170139418631。

    相关文章

      网友评论

        本文标题:APP启动过程详解+优化(二进制重排)

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