使用 Profile GPU 渲染进行分析

Profile GPU 渲染 工具指示渲染管线的每个阶段渲染前一帧所需的时间。了解这些信息可以帮助您识别管道中的瓶颈,以便您知道需要优化哪些内容来提高应用的渲染性能。

此页面简要说明了每个管道阶段发生的情况,并讨论了可能导致瓶颈出现的问题。在阅读此页面之前,您应该熟悉 Profile GPU 渲染 中提供的信息。此外,为了了解所有阶段如何协同工作,查看 渲染管线的工作原理 可能会有所帮助。

视觉表示

Profile GPU 渲染工具以图形的形式显示阶段及其相对时间:一个颜色编码的直方图。图 1 显示了此类显示的示例。

图 1. Profile GPU 渲染图

Profile GPU 渲染图中显示的每个垂直条的每个段都代表管道的一个阶段,并在条形图中使用特定的颜色突出显示。图 2 显示了每个显示颜色的含义的关键。

图 2. Profile GPU 渲染图图例

了解每个颜色的含义后,您可以针对应用的特定方面尝试优化其渲染性能。

阶段及其含义

本节说明了图 2 中每个对应于颜色的阶段发生的情况,以及需要注意的瓶颈原因。

输入处理

管道的输入处理阶段衡量应用处理输入事件花费的时间。此指标指示应用花费多长时间执行作为输入事件回调结果调用的代码。

当此段很大时

此区域的高值通常是由于输入处理程序事件回调内部执行了过多的工作或过于复杂的工作导致的。由于这些回调始终在主线程上发生,因此解决此问题的方法侧重于直接优化工作或将工作卸载到其他线程。

还需要注意的是,RecyclerView 的滚动可能会出现在此阶段。 RecyclerView 在使用触摸事件时会立即滚动。因此,它可以加载或填充新的项目视图。出于这个原因,使此操作尽可能快非常重要。诸如 Traceview 或 Systrace 之类的性能分析工具可以帮助您进一步调查。

动画

“动画”阶段显示了评估该帧中运行的所有动画程序所花费的时间。最常见的动画程序是 ObjectAnimatorViewPropertyAnimator过渡

当此段很大时

此区域的高值通常是由于动画的某些属性更改而执行的工作导致的。例如,抛掷动画(滚动您的 ListViewRecyclerView)会导致大量视图加载和填充。

测量/布局

为了让 Android 在屏幕上绘制您的视图项目,它会在您的视图层次结构中的布局和视图之间执行两个特定的操作。

首先,系统会测量视图项目。每个视图和布局都包含特定数据,这些数据描述了对象在屏幕上的大小。一些视图可以具有特定大小;其他视图的大小会根据父布局容器的大小进行调整。

其次,系统会布局视图项目。一旦系统计算出子视图的大小,系统就可以继续进行布局,调整视图的大小并在屏幕上定位视图。

系统不仅会对要绘制的视图执行测量和布局,还会对这些视图的父级层次结构执行测量和布局,一直到根视图。

当此段很大时

如果您的应用在此区域每帧花费大量时间,通常是因为需要布局的视图数量巨大,或者存在诸如 双重征税 等问题出现在层次结构的错误位置。在这两种情况下,解决性能问题都涉及 提高视图层次结构的性能

您添加到 onLayout(boolean, int, int, int, int)onMeasure(int, int) 中的代码也可能导致性能问题。TraceviewSystrace 可以帮助您检查调用栈以识别代码中可能存在的问题。

绘制

绘制阶段将视图的渲染操作(例如绘制背景或绘制文本)转换为一系列原生绘制命令。系统将这些命令捕获到显示列表中。

“绘制”栏记录了将命令捕获到显示列表中所需的时间,这些命令适用于本帧需要在屏幕上更新的所有视图。测量的时间适用于您添加到应用中 UI 对象的任何代码。此类代码的示例可能是 onDraw()dispatchDraw() 以及 Drawable 类的子类的各种 draw() 方法。

当此段很大时

简单来说,您可以将此指标理解为显示运行所有对每个无效视图的 onDraw() 调用的时间。此测量值包括分配绘制命令给可能存在的子视图和可绘制对象所花费的任何时间。因此,当您看到此栏出现峰值时,原因可能是许多视图突然变得无效。无效使重新生成视图的显示列表成为必要。或者,较长的时间可能是由于一些自定义视图在其 onDraw() 方法中包含一些极其复杂的逻辑导致的。

同步/上传

“同步和上传”指标表示在当前帧期间将位图对象从 CPU 内存传输到 GPU 内存所需的时间。

作为不同的处理器,CPU 和 GPU 具有专用于处理的不同 RAM 区域。当您在 Android 上绘制位图时,系统会先将位图传输到 GPU 内存,然后 GPU 才能将其渲染到屏幕上。然后,GPU 会缓存位图,以便系统无需再次传输数据,除非纹理从 GPU 纹理缓存中逐出。

注意:在 Lollipop 设备上,此阶段为紫色。

当此段很大时

帧的所有资源都需要驻留在 GPU 内存中才能用于绘制帧。这意味着此指标的高值可能意味着大量的小型资源加载或少量非常大的资源。一个常见的情况是,应用显示一个大小接近屏幕大小的单个位图。另一种情况是应用显示大量缩略图。

要缩短此栏,您可以使用以下技术

  • 确保您的位图分辨率不会比其显示大小大太多。例如,您的应用应避免将 1024x1024 的图像显示为 48x48 的图像。
  • 利用 prepareToDraw() 在下一个同步阶段之前异步预上传位图。

发出命令

“发出命令”部分表示发出绘制显示列表到屏幕所需的所有命令所需的时间。

为了让系统将显示列表绘制到屏幕上,它会向 GPU 发送必要的命令。通常,它会通过 OpenGL ES API 执行此操作。

此过程需要一些时间,因为系统会在将命令发送到 GPU 之前对每个命令执行最终转换和裁剪。然后,GPU 端会出现其他开销,它会计算最终命令。这些命令包括最终转换和附加裁剪。

当此段很大时

在此阶段花费的时间直接衡量了系统在给定帧中渲染的显示列表的复杂性和数量。例如,执行许多绘制操作,尤其是在每个绘制图元固有成本较小的情况下,可能会延长此时间。例如

Kotlin

for (i in 0 until 1000) {
    canvas.drawPoint()
}

Java

for (int i = 0; i < 1000; i++) {
    canvas.drawPoint()
}

发出比以下内容要昂贵得多

Kotlin

canvas.drawPoints(thousandPointArray)

Java

canvas.drawPoints(thousandPointArray);

发出命令与实际绘制显示列表之间并不总是存在 1:1 的关联。与“发出命令”不同(它捕获向 GPU 发送绘制命令所需的时间),“绘制”指标表示捕获发出的命令到显示列表中所需的时间。

这种差异产生的原因是系统尽可能地缓存显示列表。因此,在某些情况下,滚动、转换或动画需要系统重新发送显示列表,但实际上不必从头开始重建它(重新捕获绘制命令)。因此,您可能会看到“发出命令”栏很高,而不会看到“绘制命令”栏很高。

处理/交换缓冲区

Android 完成将所有显示列表提交到 GPU 后,系统会发出一个最终命令,告诉图形驱动程序它已完成当前帧。此时,驱动程序终于可以将更新的图像呈现到屏幕上。

当此段很大时

重要的是要了解 GPU 并行执行 CPU 的工作。Android 系统向 GPU 发出绘制命令,然后继续执行下一个任务。GPU 从队列中读取这些绘制命令并处理它们。

在 CPU 发出命令的速度快于 GPU 使用命令的速度的情况下,处理器之间的通信队列可能会变得满。发生这种情况时,CPU 会阻塞并等待队列中有空间放置下一个命令。这种队列满的状态通常出现在“交换缓冲区”阶段,因为此时已提交了整帧的命令。

缓解此问题的方法是减少 GPU 上执行的工作的复杂性,这与您对“发出命令”阶段所做的操作类似。

其他

除了渲染系统执行其工作所需的时间外,主线程上还会执行另一组与渲染无关的工作。此工作消耗的时间报告为“其他时间”。“其他时间”通常表示可能在渲染的两个连续帧之间在 UI 线程上发生的工作。

当此段很大时

如果此值很高,则您的应用可能具有回调、意图或其他应该在其他线程上发生的工作。诸如 方法跟踪Systrace 之类的工具可以提供对主线程上正在运行的任务的可见性。此信息可以帮助您针对性能改进。