Ultimate application performance
前言
WWDC2021有许多新的技术出现,今天针对其中的一场性能优化分享做一次内容的分享。持续的性能优化可以提高用户的体验,使得用户停留在APP的时间增加,所以对我们意义重大。下面聊聊优化所需的内容
概览
所需要的工具
1.Xcode Organizer
2.metricKit
3.Instruments
4.XCTest
5.APP StoreConnectAPI
性能优化的8个方面
- Battery usage
- Launch time
- Hang rate
- Memory
- diskWirtes
- Scrolling
- Terminations
- MXSignposts
每个性能维度的小章节都有一些工具、度量指标和范例来帮助大家的开发更进一步,并确保最佳的用户体验。
第一部分:电池
如果APP消耗大量的电量,用户就会在电池UI上很明显地看到app对全部的电量消耗的贡献和APP前台和后台活动。通过电池优化,用户就可以更长时间的使用我们的APP
这里是一些不同的子系统需要注意,当需要优化电池生命的时候:

其中主要是CPU、Networking和Location
我们可以在开发或是在版本发布后的时候使用一些不同的工具跟踪和分类电池的使用情况,要实现这个目的首先选择debug导航栏,长得像个小瓶子的:

然后可以看到能量度量面板 这里允许我们计量一些高CPU利用单元和 CPU唤醒开销。高CPU利用是指CPU利用率大于20%,CPU唤醒开销是指一些区域从空闲状态被唤醒所带来的能量消耗

通常我们会看见一些尖峰出现在CPU使用时间线中,例如: 当APP在绘制用户界面的时候、处理来自网络的数据或者是执行计算的时候,但是一旦这些任务完成,我们的APP等待用户执行它们的下一个动作的时候,CPU使用率就应该降低至0或者接近0的状态。

我们也可以点这里让我们的app在仪器中绘制一下使用简况

这里可以查看发热状况、CPU使用状况、测量期间的活动调用堆栈 ,也可以使用位置模块来衡量位置对于能量的影响 并保证app不使用位置在它不应该使用的时候。
MetricKit
Beta app或者发行版本app中会有一些bug,很难在本地复现或者需要更多的log和内容去debug,这个时候就要用到MetricKit
MetricKit: 一个在设备上操作的集所有于一身的性能遥测框架,能够帮助我们缩小根本原因的范围,提供给我们一些用户正面临的问题的有价值的洞察。
为了使用这个MetricKit 我们所需要做的只有
-在我的APP中添加并实现一个自定义的AppMetrics类,并使得它遵循 MXMetricManagerSubscriber 协议
-然后添加一个自定义类的引用到manager,同时要在自定义类析构的时候,移除对类的引用,这是推荐的一个最佳实现
-然后在didReceive方法中处理收到的度量数据,两个方法分别处理日常的度量数据和诊断的度量数据

一旦做完这些阶段,我就可以增加许多和我将在组织面板中找到的相同的数据, 比如能量日志、CPU度量和从 MetricKit中获取的有关当问题发生的时候什么可能已经发生错误的上下文的相关数据。
同时,归功于设备上的分析传输路径,不需要额外的努力你就可以获取到一份这个数据的简化版本:
苹果会从同意收集的设备上收集性能数据,存储在苹果的服务器,并通过苹果多种工具中的一个传输给你比如 XCode organizer。
之后就和导航到菜单栏一样容易:当XCode打开的时侯,在window中点击Organizer来启动XCode Organizer, 一旦进来 就可以选择 Battery Usage度量指标来看自己APP在最后16个app版本中的合计数据。一些细节分类的主要组成显示在右侧的图表当中:

回退面板
如果我APP的最新版本的主要指标有一个回退,那么当版本在Organizer中出现后我就会立刻知道这个情况,如果是在XCode13上,会有一个回退面板,这个面板会把这个版本显著增加的一些因素分离出来,这样我就可以在一个地方查看所有关注的信息:

要确定我的APP哪些区域会造成这些问题,我可以在报告下的Energy orginzer看到高CPU利用的一些区域和从同意收集的用户设备上收集到的一些日志,这提供了一个我APP内正在发生什么的一个更详细的情况描述,我们也可以通过请求APP Store Connect API和对请求返回数据运行自己的JSON载荷分析来获取到全部的Data , 所有的这些工具都让我有一个更容易的方法去捕获和解决我APP中大量电池使用退化的问题。
了解更多
-为了进一步提高电池生命优化可以关注WWDC2019 Improving battery life and performance
-学习更多工具使用可以查阅WWDC 2021 Analyze HTTP traffic in Instruments
挂起率和滚动
挂起指的是APP不响应用户的输入或者动作至少 250毫秒的情况, 这会导致用户从APP切换器中离开APP, 是你APP用户体验的一个障碍,应该优先考虑。
卡顿:卡顿发生在滚动过程中下一次的屏幕刷新内容还没准备好,这会导致用户缺乏用户体验且带来总体上的懊恼感,导致用户花费在APP上的时间减少。
作为APP开发者,我们的目标是最大化用户的接触数量,所以这是我们开始优化的一个好的起点 ,瞄准流畅的滚动是为了用户的最大利益, 我们可以在XCode organizer中挂起和滚动的各自面板里跟踪挂起和滚动的度量指标。

一个意味着我需要注意我APP中正在做什么的标志是,如果我注意到要么右边的图表向上走或者是在滚动的面板下,如果我注意到图像展示了更多黄色和红色的条而不是绿色的。因为从图表右侧的文件提示可以看到, 红色条意味着更差的滚动体验,应该被及时修正。
此数据可通过App Store Connect API来获取
线程状态和系统调用跟踪
我们可以通过工具来检测挂起的原因,像通过线程状态或者系统调用跟踪,线程状态跟踪工具展现了线程状态的一个时间线和操作系统什么时候调度线程来运行,我可以在详情面板看到线程被阻塞了多长时间。系统调用跟踪展示了一个系统调用进入和它们占用了多长时间的细节。

使用XCTest来测试滚动
为了验证我不发布会影响到我用户滚动体验的有bug的版本, 我可以用XCTest来写一个性能测试,来启动和滚动我的应用程序。在这个例子中我指定要测量滚动减速曲线,这个子度量指标

因为measure block 默认会运行5次,所以我会在每次measure函数中使用XCTMeasuerOptions 重置app的状态(第5行)我可以把这个传入到measure block来停止测试,然后重置app状态(最后一行)
iOS15MetricKit改进
有时,在加速测试用例中不容易重现那些响应性问题。幸运的是MetricKit 部署在我的生产应用上时可以允许我收集遥测和诊断这些问题,在挂起方面 ios14上 MetricKit会把这些诊断以24小时的间隔传送给我:

在ios15上 我会在问题发生后马上收到接收到所有的诊断,包括挂起

在滚动衔接方面 ios15 在MetricKit内引入了一个新的API: 使用MXSignpost来给自定义的动画打tag
MXSignpost是一个封装API,桥接到MetricKit的,允许我在遥测时给一些关键代码部分打一些tag
使用 MXSignpostAnimationIntervalBegin API我就可以有策略的标记自定义动画的开始 ,使用MXSignpost end API 我可以标记动画的结束和收集在那个期间的滚动衔接率遥测,这两个函数不仅捕捉这期间的细粒度性能数据而且也会捕捉发生的任何故障。
了解更多
为了更好的理解和消除你APP内挂起,推荐查阅下WWDC2021 “Understand and Eliminate Hangs from your App”演讲
为了深入细节关于怎么分辨滚动故障,我推荐查阅WWDC2020“Eliminate Hitches Using XCTest” 和WWDC2020“Explore UI animation hitches and the render loop”技术演讲
到目前为止我们接近了一半路程的标志。我们现在继续讨论磁盘写入。
磁盘写入
磁盘写入会消耗我用户的闪存, 也会降低设备健康。写入也会占用大量时间、降低用户体验,如果频繁的执行写入会降低性能,所以批处理这些写入是很重要的。
在发布自己APP版本之前,我可以使用仪器中的文件活动模版验证我的APP

这个工具以系统调用的方式记录文件系统的使用,所以我可以轻易的分辨我APP代码中哪里进行了文件系统访问
磁盘写入分析测试
有很多方法可以优化这个APP的写入和限制写入磁盘
一些通用的方法是分批处理写操作 对频繁改变的数据使用Core Data 避免重复的文件创建和删除操作。
除了分析我的APP ,我也可以用XCTest写性能测试来衡量我APP的磁盘使用,从而防止在用户设备上运行过多的磁盘写入代码
下面是一个例子传入了一个XCTStorageMetric实例到measureWithMetricsAPI,然后会激活写入磁盘的代码,测试衡量block中代码写入磁盘的数据量,并将结果在XCode自身中显示出来

我可以设置一个我期望的写入磁盘的数据数量的基准线,这样如果blcok中的代码写入超过基准线的数据就会使得测试失败,这就会帮助我确保不会向外发送任何有问题的代码
如果我已经发布了一个版本其中有高的磁盘写入,我可以使用Organizer来追踪它在用户设备上的性能,磁盘写入度量指标向我展示了我app现在的版本正在进行多少写入、和之前发布的版本相比的趋势,图表中的峰值表明了我程序中存在造成大量写入的bug

我应该分辨出这些写入的主要来源,理解它们,并找出一些方法来减少它们
查找这些写入的来源可以通过查看磁盘写入报告

这些是一些在我的app在24小时内写入超过1GB的数据时的额外报告 堆栈追踪展示了我的代码中哪里进行了多余的写入
在XCode 13上 也可以在insights中得到更多的细节 这里会给我指出我可以做的一些简单的优化来成为一个好APP,减少我APP的一些写入

了解更多
想了解更多如何无缝分辨和解决磁盘写入问题,可以收看WWDC2021 Diagnose power and performance regressions in your APP regressions
接下来我们讨论一下启动时间和中止
启动时间和中止
启动时间指的是用户点击你的app icon和第一帧被渲染在你的APP上之间的时间间隔,如果你的用户在等待启动的过程花费了很长时间 ,无意间会导致用户的沮丧感觉, 延长的启动时间会导致系统中止掉你的APP, 当系统中止掉你的APP后你的用户会重新经历一个从开始的整个启动流程,这会花费比从后台运行状态恢复前台更长的时间
进程退出的发生可能有很多原因 比如达到或者超过系统内存限制、启动时间超过限制,每次APP因为这些原因终止的时候,下次用户点击你APP的icon时会经历整个启动流程,这不仅会花费很长时间,而且也是一个令人沮丧的用户体验,尤其是它频繁发生的时候
如果你不恢复状态,让用户重新找到他们原先的地方或者重新创建他们丢失的工作也是一个沮丧的体验
如果启动问题已经存在于我的用户正在使用的版本中,所以我们可以从Organizer着手,在启动时间面板和新的中止面板看一下,

启动时间面板可以告诉我我的app在最近的16个版本中展示第一帧平均所需的时间,所以我可以精确的看到新的功能添加前启动时间是多少
也可以到新的中止面板中看到我的app因为启动时间太长被系统中止的频率是多少

我可以在桌面使用工具里的APP启动模版来验证我APP的启动时间从而测试这个问题, 这个模版会运行我的APP5秒
在此期间,它收集了一个时间配置文件和APP启动时候正在运行的线程状态追踪,所以我可以找出为什么线程被阻塞了,然后解决这个问题

当然,也可以在性能XCTest中测量启动时间,通过在一个测量block中像我们先前见到的那样使用XCTApplicationsLaunchMetric ,如果我希望用APP内实现的MetricKit做我自己的分析,我会收到日常度量负载,默认情况下其中一部分是中止遥测
了解更多
为了了解更多关于APP中止的时候状态恢复如何避免数据丢失,可以观看 WWDC 2020 Why is my app getting killed?
接下来是我们今天的分享的最后一个话题:内存
内存
内存是app、操作系统、内核间共享的资源,如果app 达到内存使用限制就会被系统中止,下次用户启动就会经历一个完整的启动流程,这个维度我们可以关注organizer里内存和中止两个度量指标

虽然这看起来不会被中止,但是在新的版本中这里有一个大的内存使用峰值,从organizer 中的峰值内存和暂停APP的内存占用这两个分类中就可以看出。
工具验证
我可以通过工具中的Leaks、Allocations和VM 追踪模版来验证我的内存使用:
leaks将会验证我进程的堆,检查我泄漏的内存

Allocations将会检查我APP的内存生命周期

VM Tracker将会展示随着时间的流逝我app的虚拟内存空间

MetricKit运行自己的分析
我同样可以使用MetricKit获得相同的信息 并在此基础上运行我自己的分析,除了使用我的包含终止和内存遥测的日常度量负载 我也可以在关键代码周围使用MXSignposts

新手
如果我们是性能测试工具方面的新手
可以查阅WWDC2020 Diagnose performance issues with the Xcode organizer 和 WWDC2020 What‘s new in MetricKit
WWDC2020 Identify trends with Power and Performance API 和 WWDC2019 Getting started with the insturments
概要
今天分享了5个方面的内容
-性能优化面临的挑战
-性能优化的工具
-优化度量指标
-性能问题分析
-定义自己的性能测试
在钻研这些工具之后希望你能得到你需要的资源去提交性能优秀的代码到APP Store。