(译)Analyze with Profile GPU Rendering

翻译自Analyze with Profile GPU Rendering


疑问:

  • Unlike Issue Commands, which captures the time it takes to send drawing commands to the GPU, the Draw metric represents the time that it took to capture the issued commands into the display list.

如何理解这一句?

Profile GPU Rendering tool 表示渲染前一帧时每个阶段消耗的相对时间。可以使用这个相对时间用于定位渲染流水线中的性能瓶颈,并用于优化app渲染性能。

本文简单介绍每个流水线阶段发生了什么,并且讨论了可能引起性能瓶颈的问题。读本文前,你应该先熟悉Profile GPU rendering中的内容。另外,想想从整体上了解每个阶段是如何工作的也可以参考how the rendering pipeline works.

Profile GPU Rendering tool 在图表中展示每个阶段和它们的相对时间:彩色直方图。

图中每个直条表示流水线中的一个阶段,每个阶段使用特定的颜色高亮。

一旦你理解每种颜色的含义,就可以针对app中特定目标进行渲染性能优化。

不同阶段及含义

Input handling

input handling 阶段用于测量 app 花了多久时间处理 input events。这个值表示 app 花了多少时间用于执行 input event 回调中的代码。

当这个阶段的时间变多时,通常是因为在 input-handler event 回调中执行太多或太复杂的工作。而这些回调一般总是在主线程中,所以解决方法是优化代码本身,或将代码移到工作线程。

另外要注意的是,RecyclerView 的滚动也是发生在这个阶段。RecyclerView 消耗 touch event 时会马上滚动。而滚动后,RecyclerView 会 inflate 或 populate 新的 item views。所以,inflate 或 populate 操作要尽可能快。Traceview 或 Systrace 可以用于更深入的分析。

Animation

动画阶段表示你在这一帧中使用 animator 花了多少时间。常用的 animator 包括:ObjectAnimator,ViewPropertyAnimator 和 Transitions。

当这个阶段的时间变多时,通常是因为动画一些属性变化引起某些工作的结果。比如,在滚动 ListView 或 RecyclerView 时 fling 动画会导致大量的 inflate 或 populate 操作。

Measurement/layout

Android 将 view 绘制到屏幕上时,会在 view hierarchy 中的 layout 和 view 上执行两个特定的操作。

首先,系统测量 view items。每个 view 和 layout 有特定数据用于描述对象在屏幕上的大小。一些 view 有特定大小,另一些则适应父容器的大小。

其次,系统对 view items进行布局。一旦系统计算完每个 children view 的大小,就会继续对它们进行布局。

系统不仅对将要绘制的 view 进行测量和布局,也会对这些 view 的父容器进行测量和布局,直到 root view。

当这个阶段的时间变多时,通常是要么是因为要处理的 view 太多,可者 view hierarchy 遇到了 “double taxation” 问题。

onLayout(boolean, int, int, int, int)onMeasure(int, int) 中的代码也会导致性能问题。Traceview 和 Systrace 可以帮助检查这些回调,以定位代码问题。

Draw

绘制阶段将 view 上的渲染操作,比如绘制背景或绘制文本,转换成一系列的绘制命令。系统将这些命令放在 display list 中。

这个阶段记录了将绘制命令放在 display list 中的耗时(用于更新到屏幕上的所有 view 的绘制命令)。这个时间适用于任何你添加到 UI 对象中代码。比如,onDraw()dispatchDraw(),以及不同的 Drawable.draw() 方法。

当这个阶段的时间变多时,简单来说以将这个值视为为每个 invalidated view 调用 onDraw() 方法的耗时总和。这个值包含分发绘制命令到 children 以及 drawable 的耗时。基于这个原因,它这阶段耗时变多时,也可能是因为一堆 view 突然执行 invalidate()。让 view 无效会导致重新生成 view display list。另外,在自定义 view 的 onDraw() 方法中执行复杂逻辑的自定义 view 也会导致耗时变长。

Sync/upload

Sync & Upload 值表示这一帧中将 bitmap 对象从 CPU memory 传输到 GPU memory 的耗时。

作为不同的处理器,CPU 和 GPU 有不同的专用 RAM 区域。它你绘制 bitmap 时,系统会在渲染 bitmap 之前 将其传输到 GPU memory。接着 GPU 会缓存这个 bitmap,以免系统重新传输数据,除非这个在 GPU 中的缓存数据被清理。

当这个阶段的时间变长时,一帧的所有资源都在 GPU 内存中才能绘制这一帧,较大的值表示加载了大量的小资源或少量的过大资源。一个典型的场景是 app 展示了一个接近屏幕大小的 bitmap。另一个场景是 app 展示了很多小图标。

要减少这个阶段的耗时,可采用的技巧如下:

  • 保证 bitmap 的分辨率不会比将要展示的大太多。比如,你应该避免在一个 48x48 的 ImageView 上展示一个 1024x1024 的 bitmap
  • 使用 prepareToDraw() 方法在下一帧之前异步预加载 bitmap

Issue commands

The Issue Commands segment represents the time it takes to issue all of the commands necessary for drawing display lists to the screen.

示发送所有的必要 display list 绘制命令的耗时。系统将 display list 绘制到屏幕时,它会向 GPU 发送必要的命令。通常是通过 OpenGL ES API 进行发送。

这个过程需要一些时间,因为系统要在发送命令到 GPU 前对每个命令进行最终的转换和裁剪。另外的开销来自 GPU 侧,它会计算最终的命令。这些命令包含最终的转换和附加的裁剪。

当这个阶段的时间变长时,这个时间是对系统在给定的帧进行渲染时 display lists 复杂性和数量的直接度量。比如,进行非常多的绘制操作,特别是过多无关的原子绘制,会极大的增加这个时间。比如:

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

上面这个代码就比下面的代码要耗时得多:

1
canvas.drawPoints(mThousandPointArray)

发送命令跟实际绘制 display lists 并不总是 1:1 的关系。Unlike Issue Commands, which captures the time it takes to send drawing commands to the GPU, the Draw metric represents the time that it took to capture the issued commands into the display list. (?? 没太明白)

系统会尽可能缓存 display lists,导致差异更大。所以在滚动、transform、以及动画场景下,系统会重新发送 display list,但并不用真的重建 (As a result, there are situations where a scroll, transform, or animation requires the system to re-send a display list, but not have to actually rebuild it—recapture the drawing commands—from scratch)。所以你会看到 “Issue Commands” 升高的同时 “Draw” 并没有变高。

Process/swap buffers

当 Android 将所有 display list 提交给 GPU 后,系统会发送一个最终的命令通知 graphics driver 这一帧已经完成。这时起,driver 就可以将更新的 image 显示成屏幕。

当这个阶段的时间变长时,需要理解提 GPU 是跟 CPU 并行工作。Android 系统发送命令给 GPU,并进入下一个任务。而 GPU 从队伍读取绘制命令并进行处理。

当 CPU 发送绘制命令的速度比 GPU 的处理速度快时,处理器之间的通信队列满了。这种情况下,CPU 阻塞并等待直到队列有新的位置给下一条绘制命令。队列满的状态经常发生在 Swap Buffers 阶段,因为这个时间点整个帧的绘制命令被提交。(This full-queue state arises often during the Swap Buffers stage, because at that point, a whole frame’s worth of commands have been submitted.)

解决这个问题的关键是降低 GPU 中工作的复杂性。

Miscellaneous

渲染系统除了花时间处理其工作,还有一些额外的工作发生在主线程中所以不能渲染(there’s an additional set of work that occurs on the main thread and has nothing to do with rendering)。这些时间被统计为 misc time。Misc time 通常表示两个连接帧之间 UI 线程发生的工作。

如果这个值变高,很可能是app有某些回调、intents或其他工作应当放在工作线程处理。Method tracing 和 Systrace 可以用于分析主线程中运行的任务,这些信息有助于进行性能优化。