案例研究: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 应用启动自定义指标固定到信息中心顶部以便于分析。

请注意,即使您正在使用 reportFullyDrawn()Android 应用启动指标也表示首次显示时间。要确定完全显示时间,请在 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. 评估 RunnableRunning 状态的相对时间,以初步了解 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 回复的系统进程,在此案例中为 "Binder:687_11 [2542]"。点击相关的系统进程以获取有关 Binder 事务的更多信息。

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

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

结束状态显示 Runnable (Preempted),这意味着该进程正在被延迟,因为 CPU 正在执行其他操作。要找出它被什么抢占了,请展开 Ftrace Events 行。在可用的 Ftrace Events 选项卡中,滚动并查找与感兴趣的 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 活动。要解决此问题,请将绘制第一个帧后不久发生的事件添加到基准配置文件中,方法是将配置文件收集扩展到应用准备好使用的点。在许多情况下,您可以通过在基准配置文件收集宏基准测试的末尾添加一行来做到这一点,该行会等待特定的 UI 小部件出现在您的屏幕上,表明屏幕已完全填充。

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

结果

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

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

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

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

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