启动优化,我们有点“牛”之理论篇

456 阅读13分钟

1.背景与挑战

在上一篇文章《体验优化,我们有点不一样》中,我们详细地介绍了整个移动互联网“流量红利见顶,但时长红利可期”的大背景。在这样的大背景下,给每个App带来的挑战是要转变传统的流量思维,深耕用户,挖掘单用户的价值。

下面的用户行为动线图,把抽象的挑战拆解为用户具体的行为特征。从图中可以看出,用户从下载安装、到启动、再到最后的交易转化,中间可能会遇到诸多体验问题。虽然看似繁杂和琐碎,但可以再一次抽象为性能、稳定性和交互体验三大方面。为了阻止这三大体验问题带来负面的影响,需要我们优化App性能、提升App稳定性和提高用户满意度指数。

image.png

回归到本文启动优化的主题上,从上面用户行为动线图中可以看出,启动作为用户体验App的第一关键环节,启动时间的长短,直接关系到用户对App的第一印象。甚至如果启动时间过慢,一些用户会直接跳失,造成一定的业务损失。

1688作为国内第一大B类电商平台,服务近千万的B类买家。随着这些年不断的技术迭代,也欠下了一些“性能债”。在今年集团对移动应用体验十分重视的契机下,我们也启动了体验优化专项。其中第一项的攻坚点就是启动优化。下面会详细介绍我们优化的方案、效果以及总结和展望。

2.优化方案

2.1 通用优化整体原则

在介绍具体方案前,我想简单介绍一下优化的整体原则。因为无论是启动优化,还是流畅度优化亦或其他的性能优化,在琐碎繁杂的具体优化手段之上,都有一些共性和通用原则。只有掌握了这些基本原则,才能够在具体优化过程中,不至于迷失于细节之中,做到大方向正确、事半功倍。

不要提前过度优化

其实不止性能优化,软件开发过程中,在诸如架构设计、设计模式、敏捷开发等领域都有类似的原则。核心思想就是在满足核心的功能指标和非功能指标的前提下,要尽可能保持软件整体结构的简单性,不要过度设计。大家耳熟能详的KISS (Keep it Simple, Stupid) 原则也阐述了这个道理。

在性能优化领域,这个原则包括优化的过程中需要避免的两个陷阱,即提前优化和过度优化。

  • 提前优化指的是在开发的起始阶段,在没有合理的用户数量、真实运行环境评估和线上性能数据指标的前提下,为了达到性能单一非功能指标,而提前做了很多盲目优化工作。

  • 过度优化是指为了优化性能,过度增加系统复杂度和维护成本,使得开发周期变长。虽然可能性能上带来了一定的提升,但是和过度优化而导致的这些缺点来比,这么做显而易见是得不偿失的。

要定位应用性能瓶颈

在做优化前,首先一定要定位到应用的性能瓶颈,然后依性能严重程度逐个解决。不要盲目优化,否则最后可能花了很大的力气,优化掉的可能只是性能损耗很小的一部分。这一原则我觉得尤为重要,因为在工作中也遇见过包括我在内,很多不进行性能瓶颈定位,全凭主观猜测进行性能优化的情况。 在寻找性能瓶颈过程中,也需要注意以下问题:

  • 不要主观猜测,让性能评测数据说话。

  • 要使用恰当的性能评测工具和评测方法。

  • 要抓重点,才能有的放矢,重点解决核心瓶颈问题。

针对第二点这里展开介绍一下。

工欲善其事必先利其器,好的评测工具可以更快定位性能问题。iOS评测工具主要分线上和线下两大类,有些工具在线下和线上都有覆盖,这里简单列举几个常用的优化工具。

  • instruments。对于开发版本的性能优化,Xcode提供的instruments绝对是最好的寻找性能瓶颈的工具之一。instruments有丰富的性能评测工具,包括常用的Core Animation、Time Profiler、Leaks和Allocations等等,新版instruments还提供了App launch,可以更方便定位启动性能问题。

  • 火焰图。火焰图可以让软件执行情况可视化,是性能分析、调试的利器,最早来源于Brendan Gregg写的论文《The Flame Graph 》。在后端、前端、Android等平台已经有比较成熟的工具。在iOS端侧苹果没有给开发者提供官方的火焰图工具,但是还是有一些开源的火焰图解决方案。诸如everettjf的Messier(需要越狱设备)。阿里内部淘宝团队,也提供了一个无需越狱,微侵入的解决方案AliHAMethodTrace

  • 关键节点时间戳。可以根据应用特点,在关键节点打上时间戳用来线下和线上的性能分析。

  • APM(Application Performance Management)。目前各大厂基本都有自己的APM平台,社区也有一些开源或者付费的方案,可以对线上App进行性能监控以及预警。

恰当、正确的评测方案,同样十分重要。这里简单总结如下:

  • 要使用真机,而不是模拟器。模拟器的CPU比iOS机器要快很多,所以在模拟器上,CPU相关的操作会更快。因为Mac的GPU和iOS设备上的GPU不同,所以模拟器需要在CPU上通过软件去模拟iOS设备上的GPU,所以GPU相关的操作会更慢。因此如果使用模拟器去进行性能优化的话,评测设备和真实用户设备性能表现的不一致,会导致优化的效果大打折扣。这里面内存是一个例外,在做内存优化的时候,使用模拟器和真机一般差别不大,可以使用模拟器进行内存的优化。

  • 要使用Release配置而不是Debug配置。因为在release包的时候,编译器会做一些优化以提高性能。自己的工程代码可能也会在release下做一些优化,比如去除log信息和一些debug功能等。我们关心的是release下的性能,因为用户最终也是使用的release的安装包。所以测试的时候要一定要记住在release配置下进行,instruments进行性能评测的时候,默认是在release下进行的。

  • 要使用性能相对差的机器进行评测。因为我们需要保证的是我们的应用在性能差的机器上也有良好的表现。如果有条件,最好能够覆盖多个机型。

  • 要尽可能覆盖不同系统版本。

  • 针对不同优化目标,要了解其测试特点。比如iOS的启动优化,最准确的测量条件是重启 2-3min、正常的手机温度、退出iCloud账号等。

要在不同性能指标间权衡,达到总体最优

在已经找到性能瓶颈的时候,解决性能问题的方法则需要具体问题具体分析,要在不同性能指标间权衡,以达到总体最优。

这需要我们要有整体的意识,App的性能可以分为很多类,不同的性能指标对用户体验造成的影响也不尽相同,比如fps主要影响的是用户的滑动体验,页面加载时间和应用启动时间影响的是用户等待时间上的体验。我们在优化的过程中,要牢记我们的目标是希望App的整体体验最优,而不是某一单项的性能指标最优。不同优化指标之间可能是呈正相关,比如优化了滑动过程中大量函数的耗时时间,使得fps性能提升,可能会导致App的耗电量变少。同时,不同优化指标也可能是负相关、相互制约的,比如为了流畅性做了过多的cache,会导致内存性能下降,甚至因为memory warning导致被系统kill掉,这无疑对App的整体体验造成了负面的影响。因此实际优化过程中需要我们反复权衡利弊和取舍,达到整体的性能最优。

要理解优化任务的底层运行机制

如果不理解优化任务的底层运行机制,可能很难达到更好的优化效果。比如在做启动时间优化的时候,如果你不知道iOS中App的启动时间是由main之前和main之后两部分时间组成的,此时如果你的App是因为main函数之前的部分占用了过多的启动时间,可能你花了大量的精力去优化main之后的时间却没有达到好的优化效果。如果你不知道App启动过程的运行机制,就无法知道去检查是否链接了过多的自定义的动态库或者去load函数里面确认是否有耗时的操作等等。还有在做fps优化的时候,如果不了解卡顿的底层原因是什么、一个view从创建到显示过程中经历那些步骤、CPU和GPU在这个过程中都扮演什么角色,则很难做到丝滑般的顺畅。还有在做内存优化的时候,如果不了解内存分为哪几类、系统对App和不同类型extension的内存限制机制的不同、超过限制系统会采取什么操作等等,也很难把内存优化做好。因此只有深入了解底层机制才能更好的有针对性的提出更优的解决方案。

要有技术保障体系

性能优化不能一劳永逸,我个人觉得更是一场持久战。不仅需要你能够在某个特定时期做专项优化的攻坚战,还要为打好持久战做出好的后勤补给,为了能使App长期保持好的性能,不仅仅需要开发人员有良好的开发技能,还需要有一些技术保障和体系,在开发阶段、测试阶段和灰度&线上阶段做好性能保障。

2.2 业界启动优化方案综述

在讲方案前,首先要确定目标。对于启动优化而言,目标当然是启动时间。苹果在WWDC2019《Optimizing App Launch》里面建议把启动时间控制在400ms内,因为这是启动动画的时间;系统要用100ms的时间来初始化APP,所以留给了你300ms的时间来构建你的第一个view。

image.png

具体到开发过程就需要确定启动开始和结束两个时间戳,通过差值即可确定启动时间。定量确定了时间的作用有两个,一个是可以衡量启动优化的效果,另一个是线上做用户启动数据大盘的监控和预警。可以使用进程的启动时间来确定App的启动起始时间。App启动结束的时间确定目前尚没有统一的标准,苹果提出一种方法是用户第一帧渲染完成可以作为启动结束的时间点。如果从用户体验视角触发,我认为是第一个对用户有价值的页面加载完成才是结束点,比如对于1688客户端来说,首页加载完成作为启动时间的截止点。至于到底采用何种方案,这个可以根据自己App的特点权衡确定,只要内部达成一致即可。

在iOS端做启动优化,其实也是一个老生常谈的问题。早在苹果WWDC2016就针对启动优化做了一期介绍《Optimizing App Startup Time》,里面将启动时间划分问main函数之前和之后两部分,并着重介绍了main函数之前Load dylibs 、Rebase 、Bind、ObjC和Initializers这五个过程里面发生了什么事情,以及如何做相应的优化,目前业界做启动优化时,premain优化大多参考这期介绍。

在WWDC2017,苹果又出了一起关于启动优化的介绍《App Startup Time:Past, Present, and Future》,主要回顾了一些App启动优化的最佳实践并介绍了dyld3在性能等方面的改进。

在WWDC2019上,苹果再次推出了一起关于启动优化的介绍《Optimizing App Launch》。里面首先提及了一个我们小时候班主任的灵魂拷问:“上课你一个人浪费一分钟,全班就是45分钟时间!!” 苹果爸爸则说每次应用启动减少1ms,对全体用户而言,就会节省162天!!!以此凸显启动优化的重要性。文章里还提及了启动的分类,对我们理解优化目标和测试手段提供了重要的指导(这里强调一个测试误区,以往我们在做了某项启动优化后,会杀后台立刻启动App测试优化效果。由于此时为warm启动,会让我们很难发现优化的效果,导致优化过程中的反复和干扰)。WWDC2019因为把Dyld3开放给所有APP,所以Apple重新梳理了启动的各个阶段,并给出了对应的优化建议,感兴趣的同学可以仔细研究一下。

image.png

在iOS社区,诸多大厂也分享了他们的启动优化经验,诸如《Optimizing Facebook for iOS start time》《高德APP启动耗时剖析与优化实践(iOS篇)》《美团外卖iOS App冷启动治理》《抖音品质建设 - iOS启动优化《原理篇》》《今日头条iOS客户端启动速度优化》等等,业界做启动优化的思路也基本大同小异,基本上是围绕着苹果WWDC发布的几个专题所讲的方案进行优化。其中二进制重排是应用在iOS启动优化上的一个新的方案。虽然很早时候,在PC上二进制重排就有过应用,且苹果官方也在官网提供过相应的方案《Improving Locality of Reference》,但尚未大规模应用在iOS生产环境。抖音的《抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15%》和手淘的《手淘架构组最新实践 | iOS基于静态库插桩的⼆进制重排启动优化》为我们具体实施方案提供了借鉴意义,同时也有一些开源方案出来,比如AppOrderFiles

由于文章篇幅过长,我们的启动优化方案细节会放到下一篇文章阐述。有兴趣的同学可以查看一下。