案例研究:Gmail Wear OS 团队如何将应用启动速度提高 50%

应用启动代表了您的应用给用户的第一印象。用户不喜欢等待,因此您需要确保您的应用启动速度快。为了向您展示一个真实的应用开发团队如何发现和诊断其应用启动问题,以下是 Gmail Wear OS 团队所做的工作。

Gmail Wear OS 团队开展了优化工作,特别关注应用启动和运行时渲染性能,以满足其团队的应用性能标准。但是,即使您没有具体的阈值目标,如果您花一些时间进行调查,几乎总有改进应用启动的空间。

捕获跟踪并查看应用启动

要开始分析,捕获跟踪,其中包含应用启动,以便在 Perfetto 或 Android Studio 中进行仔细检查。本案例研究使用 Perfetto,因为它向您展示了设备系统中发生的情况,超出了您的应用范围。当您在 Perfetto 中上传跟踪时,它看起来像这样

图 1. Perfetto 中跟踪的初始视图。

由于重点是改进应用启动,因此找到带有Android 应用启动自定义指标的行;通过点击悬停在该行上时出现的图钉图标,将其固定到视图顶部非常有用。您在Android 应用启动行上看到的条形或切片指示应用启动涵盖的时间范围,直到第一帧应用绘制到屏幕上,因此您应该在此处查找问题或瓶颈。

Android App Startups row with option to pin highlighted.
图 2. 将 Android 应用启动自定义指标固定到仪表板顶部,以便于分析。

请注意,Android 应用启动指标代表的是初始显示时间,即使您使用的是reportFullyDrawn()。要确定完全显示时间,请在 Perfetto 搜索框中搜索reportFullyDrawn()

检查主线程

首先,检查主线程上发生了什么。主线程非常重要,因为通常所有 UI 渲染都在这里发生;当它被阻塞时,将无法进行任何绘制,并且您的应用看起来像是冻结了。因此,您需要确保主线程上没有发生任何长时间运行的操作。

要查找主线程,请找到包含您的应用包名称的行并展开它。与包名称相同的两行(通常是该部分的前两行)代表主线程。在这两行主线程中,第一行表示 CPU 状态,第二行表示跟踪点。将这两行主线程固定在Android 应用启动指标下方。

Android App Startups and main thread rows pinned.
图 3. 将主线程行固定在 Android 应用启动自定义指标下方以帮助分析。

在可运行状态下花费的时间和 CPU 竞争

要获得应用启动期间 CPU 活动的聚合视图,请将光标拖到主线程上来捕获应用启动时间范围。出现的线程状态面板将显示在所选时间范围内每个 CPU 状态下花费的总时间。

查看在Runnable状态下花费的时间。当线程处于Runnable状态时,线程可用于执行工作,但没有安排任何工作。这可能表明设备负载过重,无法安排高优先级任务。最顶层的、用户可见的应用在调度中具有最高优先级,因此空闲的主线程通常表示应用内的密集型进程(例如动画渲染)正在与主线程争夺 CPU 时间。

Main thread highlighted with total time in different states in Thread States panel.
图 4. 评估Runnable状态与Running状态下的相对时间,以初步了解 CPU 竞争的程度。

Runnable状态下花费的时间与在Running状态下花费的时间之比越高,发生 CPU 竞争的可能性就越大。以这种方式检查性能问题时,应首先关注运行时间最长的帧,然后处理较小的帧。

分析在Runnable状态下花费的时间时,请考虑设备硬件。由于所描绘的应用在配备两个 CPU 的可穿戴设备上运行,因此与查看配备更多 CPU 的设备相比,预期在Runnable状态下花费更多时间,并且与其他进程的 CPU 竞争也更多。即使在Runnable状态下花费的时间比典型的手机应用预期的时间更多,但在可穿戴设备的背景下,这可能是可以理解的。

OpenDexFilesFromOat*中花费的时间

现在检查在OpenDexFilesFromOat*中花费的时间;在跟踪中,它与bindApplication片段同时发生。此片段表示读取应用 DEX 文件所需的时间。

被阻塞的 Binder 事务

接下来检查 Binder 事务。Binder 事务表示客户端和服务器之间的调用:在本例中,应用(客户端)使用binder transaction调用 Android 系统(服务器),服务器使用binder reply进行响应。确保应用在启动期间不会进行不必要的 Binder 事务,因为它们会增加 CPU 竞争的风险。如果可以,请将涉及 Binder 调用的工作推迟到应用启动周期之后。如果必须进行 Binder 事务,请确保它们不会超过设备的Vsync 刷新率

第一个 Binder 事务通常与ActivityThreadMain片段同时发生,在本例中似乎相当长。要了解可能发生的情况,请按照以下步骤操作

  1. 要查看关联的 Binder 应答并了解 Binder 事务的优先级如何确定,请单击感兴趣的 Binder 事务片段。
  2. 要查看 Binder 应答,请转到当前选择面板,然后单击Following threads部分下的binder reply。如果您想手动导航到该位置,“线程”字段还会告诉您 Binder 应答发生的线程;它将在不同的进程中。将出现一条连接 Binder 事务和应答的线。

    A line connects the binder call and reply.
    图 5. 确定应用启动期间发生的 Binder 事务,并评估是否可以推迟它们。
  3. 要查看系统服务器如何处理此 Binder 事务,请将Cpu 0Cpu 1线程固定到屏幕顶部。

  4. 通过查找包含 Binder 应答线程名称(在本例中为“Binder:687_11 [2542]”)的片段来查找处理 Binder 应答的系统进程。单击相关的系统进程以获取有关 Binder 事务的更多信息。

查看与在 CPU 0 上发生的感兴趣的 Binder 事务关联的此系统进程

System process with End State 'Runnable (Preempted).
图 6. 系统进程处于Runnable (Preempted)状态,这表示它正在被延迟。

结束状态显示Runnable (Preempted),这意味着该进程正在被延迟,因为 CPU 正在执行其他操作。要找出它被什么抢占了,请展开Ftrace 事件行。在可用的Ftrace 事件选项卡中,滚动浏览并查找与感兴趣的 Binder 线程“Binder:687_11 [2542]”相关的事件。在系统进程被抢占的时间点附近,发生了两个包含参数“decon”的系统服务器事件,这意味着它们与显示控制器相关。这听起来是合理的,因为显示控制器将帧放在屏幕上——这是一项重要任务!因此,请按原样保留这些事件。

FTrace events associated with the binder transaction of interest highlighted.
图 7. FTrace 事件表明 Binder 事务正在被显示控制器事件延迟。

JIT 活动

要调查即时编译 (JIT) 活动,请展开属于您的应用的进程,找到两个“Jit 线程池”行,并将它们固定到视图的顶部。由于此应用在应用启动期间受益于基线配置文件,因此几乎不会发生 JIT 活动,直到绘制第一帧,这由第一个Choreographer.doFrame调用的结束表示。但是,请注意缓慢启动原因JIT compiling void,这表明在标记为Application creation的跟踪点期间发生的系统活动正在导致大量的后台 JIT 活动。要解决此问题,请通过将配置文件收集扩展到应用已准备好使用的点来将第一帧绘制后不久发生的事件添加到基线配置文件中。在许多情况下,您可以通过在基线配置文件收集 Macrobenchmark 测试的末尾添加一行来实现这一点,该行等待特定 UI 小部件出现在屏幕上,表明屏幕已完全填充。

Jit thread pools with 'Jit compiling void' slice highlighted.
图 8. 如果您看到很多 JIT 活动,请将基线配置文件扩展到应用已准备好使用的点。

结果

通过此分析,Gmail Wear OS 团队做出了以下改进

  • 由于他们在查看CPU 活动时注意到应用启动期间的竞争,因此他们用单个静态图像替换了用于指示应用正在加载的微调器动画。他们还延长了启动画面以推迟闪烁状态(用于指示应用正在加载的第二个屏幕状态),以释放 CPU 资源。这将应用启动延迟提高了 50%。
  • 从查看在OpenDexFilesFromOat*JIT 活动中花费的时间来看,他们启用了R8基线配置文件的重写。这将应用启动延迟提高了 20%。

以下是该团队关于如何有效分析应用性能的一些提示

  • 设置一个能够自动收集跟踪和结果的持续流程。考虑使用基准测试为您的应用设置自动跟踪。
  • 对您认为会改进内容的更改使用 A/B 测试,如果它们没有改进,则拒绝它们。您可以使用Macrobenchmark库测量不同场景下的性能。

要了解更多信息,请参阅以下资源