抖音品质建设 - iOS启动优化《原理篇》
Posted 字节跳动技术团队
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抖音品质建设 - iOS启动优化《原理篇》相关的知识,希望对你有一定的参考价值。
启动是 App 给用户的第一印象,启动越慢用户流失的概率就越高,良好的启动速度是用户体验不可缺少的一环。启动优化涉及到的知识点非常多面也很广,一篇文章难以包含全部,所以拆分成两部分:原理和实战。
本文从基础知识出发,先回顾一些核心概念,为后续章节做铺垫;接下来介绍 IPA 构建的基本流程,以及这个流程里可用于启动优化的点;最后大篇幅讲解 dyld3 的启动 pipeline,因为启动优化的重点还在运行时。
基本概念启动的定义启动有两种定义:
不同产品的业务形态不一样,对于抖音来说,首页的数据加载完成就是视频的第一帧播放;对其他首页是静态的 App 来说,Launch Image 消失就是首页数据加载完成。由于标准很难对齐,所以我们一般使用狭义的启动定义:即启动终点为启动图完全消失的第一帧。
以抖音为例,用户感受到的启动时间:
Tips:启动最佳时间是 400ms 以内,因为启动动画时长是 400ms。
这是从用户感知维度定义启动,那么代码上如何定义启动呢?Apple 在 MetricKit 中给出了官方计算方式:
CA::Transaction::commit()
启动的种类Tips:
CATransaction
是 Core Animation 提供的一种事务机制,把一组 UI 上的修改打包,一起发给 Render Server 渲染。
根据场景的不同,启动可以分为三种:冷启动,热启动和回前台。
那么,线上用户的冷启动多还是热启动多呢?
答案是和产品形态有关系,打开频次越高,热启动比例就越高。
Mach-OMach-O 是 ios 可执行文件的格式,典型的 Mach-O 是主二进制和动态库。Mach-O 可以分为三部分:
Header 的最开始是 Magic Number,表示这是一个 Mach-O 文件,除此之外还包含一些 Flags,这些 flags 会影响 Mach-O 的解析。
Load Commands 存储 Mach-O 的布局信息,比如 Segment command 和 Data 中的 Segment/Section 是一一对应的。除了布局信息之外,还包含了依赖的动态库等启动 App 需要的信息。
Data 部分包含了实际的代码和数据,Data 被分割成很多个 Segment,每个 Segment 又被划分成很多个 Section,分别存放不同类型的数据。
标准的三个 Segment 是 TEXT,DATA,LINKEDIT,也支持自定义:
dyld 是启动的辅助程序,是 in-process 的,即启动的时候会把 dyld 加载到进程的地址空间里,然后把后续的启动过程交给 dyld。dyld 主要有两个版本:dyld2 和 dyld3。
dyld2 是从 iOS 3.1 引入,一直持续到 iOS 12。dyld2 有个比较大的优化是 循环,在图中橙色部分的 mach_msg_trap
就是触发一个系统调用,让线程休眠,等待事件到来,唤醒 Runloop,继续执行这个 while
循环。
Runloop 主要处理几种任务:Source0,Source1,Timer,GCD MainQueue,Block。在循环的合适时机,会以 Observer 的方式通知外部执行到了哪里。那么,Runloop 与启动又有什么关系呢?
Runloop 在启动上主要有几点应用:
AppLifeCycleTips : 会有一些逻辑要在启动之后 delay 一小段时间再回到主线程上执行,对于性能较差的设备,主线程 Runloop 可能一直处于忙的状态,所以这个 delay 的任务并不一定能按时执行。
UIKit 初始化之后,就进入了我们熟悉的 UIApplicationDelegate 回调了,在这些会调里去做一些业务上的初始化:
willFinishLaunch
didFinishLaunch
didFinishLaunchNotification
要特别提一下 didFinishLaunchNotification
,是因为大家在埋点的时候通常会忽略还有这个通知的存在,导致把这部分时间算到 UI 渲染里。
一般会用 Root Controller 的 viewDidApper 作为渲染的终点,但其实这时候首帧已经渲染完成一小段时间了,Apple 在 MetricsKit 里对启动终点定义是第一个CA::Transaction::commit()
。
什么是 CATransaction 呢?我们先来看一下渲染的大致流程
iOS 的渲染是在一个单独的进程 RenderServer 做的,App 会把 Render Tree 编码打包给 RenderServer,RenderServer 再调用渲染框架(Metal/OpenGL ES)来生成 bitmap,放到帧缓冲区里,硬件根据时钟信号读取帧缓冲区内容,完成屏幕刷新。CATransaction 就是把一组 UI 上的修改,合并成一个事务,通过 commit 提交。
渲染可以分为四个步骤
[CALayer layoutSubLayers]
,这时候 UIViewController
的 viewDidLoad
和 LayoutSubViews
会调用,autolayout
也是在这一步生效[CALayer display]
,如果 View 实现了 drawRect
方法,会在这个阶段调用详细回顾下整个启动过程,以及各个阶段耗时的影响因素:
点击图标,创建进程 mmap 主二进制,找到 dyld 的路径 mmap dyld,把入口地址设为 _dyld_start
重启手机/更新/下载 App 的第一次启动,会创建启动闭包 把没有加载的动态库 mmap 进来,动态库的数量会影响这个阶段 对每个二进制做 bind 和 rebase,主要耗时在 Page In,影响 Page In 数量的是 objc 的元数据 初始化 objc 的 runtime,由于闭包已经初始化了大部分,这里只会注册 sel 和装载 category +load 和静态初始化被调用,除了方法本身耗时,这里还会引起大量 Page In 初始化 UIApplication
,启动 Main Runloop执行 will/didFinishLaunch
,这里主要是业务代码耗时Layout, viewDidLoad
和Layoutsubviews
会在这里调用,Autolayout
太多会影响这部分时间Display, drawRect
会调用Prepare,图片解码发生在这一步 Commit,首帧渲染数据打包发给 RenderServer,启动结束
dyld2 和 dyld3 的主要区别就是没有启动闭包,就导致每次启动都要:
本文回顾了 Mach-O,虚拟内存,mmap,Page In,Runloop 等基础概念,接下来介绍了 IPA 的构建流程,以及两个典型的利用编译器来优化启动的方案,最后详细的讲解了 dyld3 的启动 pipeline。
之所以花这么大篇幅讲原理,是因为任何优化都一样,只有深入理解系统运作的原理,才能找到性能的瓶颈,下一篇我们会介绍下如何利用这些原理解决实际问题,姓名 - 工作年限 - 抖音 - 基础技术 - iOS/android 。
相关链接
更多分享
iOS 性能优化实践:头条抖音如何实现 OOM 崩溃率下降50%+
字节跳动全链路压测(Rhino)的实践
Fastbot:行进中的智能 Monkey
今日头条品质优化 - 图文详情页秒开实践
欢迎关注「 字节跳动技术团队 」
简历投递联系邮箱「 tech@bytedance.com 」
点击阅读原文,快来加入我们吧!
抖音 Android 性能优化系列:启动优化之理论和工具篇
Rhea指占用 CPU 进行计算所花费的时间绝对值,中断、挂起、休眠等行为是不会增加 CPU Time 的,所以因 CPU Time 开销占比高导致的不合理耗时点往往是逻辑本身复杂冗长需要消耗较多 cpu 时间片才能处理完。比较常见的高 CPU 占用是循环,比如抖音启动时遇到过一个 so 加载耗时,最后定位原因是在解压 so 的时候,遍历 ZipEntry 的次数过多导致,一个可行的优化策略就是可以把 so 所在的 ZipEntry 提前,遍历完 so 的 ZipEntry 之后可以提前中止遍历,而不需要遍历剩下的无效 ZipEntry。除循环之外,反射也是导致 CPU Time 的重要原因,像在序列化/反序列化、View Inflate 时,都有大量的反射操作,反射的耗时主要是字符串去查找 Method 或者 Field,这个优化策略也可以考虑提前查找 Method 和 Field 缓存起来,或者是通过内联来降低 Field 数量等。另外一个常见的 CPU 耗时是类加载,类的加载过程包括:Load,从 Dex 文件里读取类的信息,可通过类重排优化;Verify,验证指令是否合法等,通过关掉 Class Verify 可以优化该过程,同时高版本的 vdex 也是为了优化 verify 过程而设计,在 dex2oat 的时候做 verify,verify 之后的结果保存成 vdex,后续只需要加载 vdex;Link,给 Field、Method 分配内存,按照名字排序以方便后续反射的时候查找 Field、Method 等,这个过程的优化,art 虚拟机采用了 ImageSpace 的方案进行了优化,将 Link 后的内存保存为 image 文件,后续可以直接 load 这个 image 文件,省去了 Link 过程;Init,类的初始化。 综合前述的五大耗时成因,这里举一个分析启动阶段 UI 耗时成因的例子作为实践参考,根据 UI 界面的生命周期(一般划分)——UI 构建、数据绑定、View 显示三个阶段分别进行分析: 从这个例子可见即使再复杂的场景只要我们进行细粒度的分析,都能将耗时点归入前述某一成因中。
以上是关于抖音品质建设 - iOS启动优化《原理篇》的主要内容,如果未能解决你的问题,请参考以下文章
iOS性能优化实践:头条抖音如何实现OOM崩溃率下降50%+