启动速度优化的难易程度与具体的app关系很大,基本随着用户量级和业务的增加,启动优化的难度也随之增加。因此不同的开发人员由于面对的app不同,对于启动优化的理解也往往差异很大。本文针对启动优化工作做一次深入的分析,从启动优化问题的定义,到问题的细化分解,再到具体优化的步骤和需要使用的工具,来帮助开发者高效的解决启动性能问题。文章中除了工具部分是针对Android平台之外,其余部分的思考应该是通用的。
Part 01
问题定义
启动优化是一个非常普遍的工作,很多开发同学听到这个词之后,基本上会下意识的对其进行解释:”启动优化就是提升app的启动速度“。这个理解是最直接最朴素的,但是只是涵盖了启动优化的部分内容。
整体启动优化工作可以概括为:在系统资源一定的情况下,优化应用的启动流程,使应用启动的关键路径能够最大化利用系统资源,并长期维持在稳定的范围内。
任何问题都需要首先划定边界,来帮助我们降低复杂度并确定方案实施的范围。对于启动优化来说,首先要做的就是定义要优化的app启动的起始点和结束点是哪里,这里针对不同的app,对于起始和结束的位置定义可能会有不同。通常我们建议从用户体验的角度出发,来做适合各自情况的定义。
Part 02
重中之重-数据
数据是我们做优化工作的重中之重,它指导优化工作的方向,应该以从数据中发现问题,并最终通过数据来验证问题的解决的原则进行优化工作。
所以在实施优化策略之前,所要做的第一件事情就是尽可能详细的收集启动阶段的数据,当前的启动时间是多少、启动各个子阶段耗时如何、各种异步任务耗时情况、启动阶段CPU等系统资源使用情况、进程及线程情况等等,这些数据都是需要收集的,而且需要从不同的维度来对数据进行分析。
数据梳理和收集的工作,一方面可以帮助我们梳理目前的指标,另一方面梳理数据的过程,可以从代码层面加深开发者对系统整个启动过程的了解,帮助开发者抓住系统启动的主脉络,将其了然于胸,建立数据项与代码逻辑的关联,当数据某一项发生波动时,开发者才能够直接定位到可能出问题的代码模块。
Part 03
优化思路
单看启动速度优化工作我们可以从两个方向着手,业务流程优化和系统资源的使用优化。
3.1 业务流程优化
业务流程优化,就是优化启动阶段涉及到的业务流程。这里说的业务流程优化有两方面的含义:
- 非必要的任务,即不影响app达到可用状态的任务,这种任务我们可以尽量去延迟初始化。
- 需要保证关键启动路径上面子任务的有效衔接,不存在某一任务执行时间过长,导致其他任务等待的情况。
第1点应该比较好理解,我们来解释下第2点:
整体启动过程,我们需要找出启动流程的关键路径,而关键路径往往是由多个不同流程组成。关键路径上面的任务、流程如果有等待的情况,那么就是我们需要优化的地方。
比如,一个启动时有闪屏广告的app,启动时需要拉取广告数据,并在广告展示结束后才进入主界面。那么广告数据的获取和获取后的展示,就可以理解为启动阶段的两个关联的子任务,如果在主线程进入到广告展示阶段后,广告数据依然没有返回,这里就会存在等待广告数据返回的时间,也就是子线程(任务)之间没有有效的衔接。
业务流程优化这部分大家最关注的就是各种启动任务的管理,很多分享的文章都介绍了启动任务异步管理框架的设计。这里不再重复这部分内容,我们补充下与任务管理相关的其他方面的工作:
- 进程的管理,以及不同进程的启动任务管理。在Android系统中,多数大型app都是多进程app,那么主进程外的各个进程的初始化时机就应该成为启动优化关注的点,这个在系统资源优化中也会提到。而且任务框架需要能够支持对于不同的进程配置不同的启动任务,同时同一个启动任务在不同进程中支持不同的执行方式(同步或异步)。
- 启动框架应该具有任务执行时间、任务等待时间、整体任务吞吐情况的统计功能,甚至可以根据数据调整本身的并发线程数量。
- 将启动过程划分为不同的阶段,任务也归为不同的阶段执行。
3.2 系统资源优化
业务流程优化与系统资源使用优化并不是分割开来的,业务流程优化本来就是为了保证我们最有效的利用系统资源,而系统资源使用优化是从另一个角度来让我们审视当前的业务流程是否合理。
系统资源很多数据指标不好衡量,比如如何衡量启动阶段cpu的利用率,如何衡量线程的执行效率等。所以这部分工作线上数据收集比较困难,开发者应该主要着手完善开发工具,通过工具来发现问题。
系统资源优化主要看以下方面的指标:
- CPU使用是否合理,关注线程锁竞争问题、主要线程获取cpu时间片情况、主要线程执行状态(runnable、running、sleep)、主进程获取cpu时间是否不足、线程是否过度竞争等。这里面会涉及到对锁竞争的发现和处理、对线程优先级的处理、对进程启动时机的把控、IO是否有阻塞线程等优化方向。
- IO使用是否合理(包括网络和本地IO),关注是否存在频繁IO,是否有启动阶段不必要的IO操作,IO是否引起主要线程阻塞,是否有大文件读写等。
- 线程情况,包括线程池使用是否合理,线程数量是否过多,线程间任务协作是否存在延迟等。
- 内存情况,主要观察是否有过多的gc问题,启动阶段heap内存是否占用过多等。
以上两个方向的工作,基本上涵盖了启动速度指标优化的绝大部分工作内容。值得指出的是,上面的工作并不是说将所有的方面都优化一遍就结束了,而是需要经常回头看,因为随着优化工作的进行,启动的状况是会发生变化的(比如之前没有冲突的锁,可能开始发生冲突)。
另一个值得注意的点就是,针对每一个具体的优化策略,开发者应该在本地实验环境下进行充分的测试,例如针对高中低端机型、不同网络类型等来评估优化策略所能带来的收益。由于线上机型各异、网络情况也比较复杂,很多时候策略上线后并不能达到预期中的效果,所以每一个优化最好通过线上AB实验来对比观测数据。
3.3 性能持续保障——与熵的对抗
启动优化很重要的另一个方面就是如何持续的保障当前优化的效果不随着功能的迭代而恶化。我们对于流程和资源使用的优化,本质上是使代码在执行过程中保证一定的有序性,这种有序性保证了启动关键链路对资源的使用率。 但是随着代码的增加,app业务越来越多,这种有序性是非常容易被打破的。根据熵增定律,如果我们不采取措施,那么系统肯定会向着混乱的方向发展,也就是说无论你之前花了多大的力气优化,系统性能总会逐步恶化,不处理解决这种恶化趋势那么就会前功尽弃。
那么如何与熵增进行对抗?在我们的日常生活中,一条马路不定时的会有路段处于修整的状态,这种修整就是对抗熵增的方法,也是我们工程上要采取的策略——发现并解决问题。但是在代码中,问题的发现却不能够那么直观,数据可以告诉我们性能发生了恶化,但是具体恶化点在哪里,往往需要我们花费很大的力气去定位。所以在持续保障方面,我们要做的就是如何尽早的发现问题和尽快的定位解决问题。
持续保障机制需要建立一套实验室性能测试环境:
- 能够建立性能基线,在日常开发中能够及时发现代码合入引发的性能问题,直接定位到引起问题的MR,有效减少定位问题的复杂度;
- 在灰度阶段持续监控线上性能数据,在上线前checkList把控版本性能影响;
- 线上针对性能建立数据指标&报警机制,监控线上功能变更引起的性能问题;
- 阶段化数据指标,直观反映变更对性能的影响区间,缩小问题定位范围。
总体来说,针对启动性能优化,我们要建立下图所示的工作流程,来保证优化工作的效率、有效性和持续的效果:
图片
上面我们主要列出了启动优化工作主体的思考方向,并没有涵盖这些方向下面所有的优化点。值得指出的是我们在制定优化策略时,需要根据各自app的情况因地制宜, 根据自身的情况来发现问题点,并确定优化策略的优先级,哪些策略要优先上,哪些策略没有必要实施(考虑ROI); 同时需要事先评估各个策略的收益,而不是将所有的策略列出来一股脑儿的挨个优化。
Part 04
优化工具(Android)
工欲善其事必先利其器,在优化工作中我们通常会遇到类似以下问题:
- 启动流程链路长、任务多、代码复杂,需要花费很大力气理清整体启动链路;
- 很难度量启动过程中系统资源情况,例如cpu使用情况、多线程锁竞争、内存、IO等;
- 无法准确定位启动过程中的高耗时函数或者瓶颈流程;
- 在优化策略上线前,无法准确衡量实施优化策略后的具体收益;
- ......
针对线程优化、IO优化等专项,有类似Matrix这种工具,可以比较好的帮助我们发现问题。我这里想重点介绍下Android提供的Perfetto工具,因为它可以帮助我们很好地了解系统的整个启动过程,以及各种系统资源的使用情况。并且基于Perfetto提供的api,我们可以开发出强大的自动分析工具,帮助我们在发现问题、定位问题、优化策略效果评估等方面产出自动化的工具。
4.1 Perfetto功能介绍
首先Perfetto提供的可视化工具,可以帮助我们从各种不同的角度来分析app运行的一段时间内的情况,简单举几个例子:
查看一段时间内,各个进程占用CPU时间情况:
图片
查看一段时间内,不同进程内线程占用CPU情况:
图片
显示一段时间内线程执行状态信息:
图片
除了上面例子外,可视化工具还可以分析多线程锁竞争问题、文件IO问题、Heap内存变化等情况,对于日常的优化工作有很大帮助。上面只是举例子,很多功能还需要实际使用去进一步体验。
Perfetto真正强大的地方并不是提供的可视化工具,而是它提供了一套对数据进行收集和分析的能力:
- 将收集的系统数据做了组织整理,并将数据以一些可以支持SQL查询的数据表暴露给开发者,进而支持我们对数据进行自定义的分析SQL数据表;
- 提供了Tracing SDK, 可以让app开发者向perfetto-trace文件中添加自定义的事件,在分析时使用TracingSDK;
- 提供了Python API,使开发者可以基于python对trace文件进行分析trace-analysis。
实际上Perfetto提供给我们一套可以实现自动化分析app性能、发现性能问题的工具的能力,并且如果将这种自动化能力与我们日常的开发流水线结合,对于性能问题的尽早发现、防劣化等能力建设都会有很大的帮助。
随着应用版本的迭代,应用启动数据会随着发生变化,因此启动优化工作也需要长期迭代,做到对启动体验的持续保障。本文主要针对启动优化工作的思路、实施方法和工具等方面进行了总结,在实际工作中不同用户规模的应用所面临的优化问题也各不相同,希望这里的总结能够对大家有所启发。