检查跟踪

CPU Profiler 中的跟踪视图提供了多种方式来查看所记录跟踪中的信息。

对于方法跟踪和函数跟踪,您可以直接在“线程”时间轴中以及“分析”窗格的“火焰图”、“自上而下”、“自下而上”和“事件”标签页中查看“调用图”。对于调用堆栈帧,您可以查看已执行的代码部分以及调用它的原因。对于系统跟踪,您可以直接在“线程”时间轴中以及“分析”窗格的“火焰图”、“自上而下”、“自下而上”和“事件”标签页中查看“跟踪事件”。

鼠标和键盘快捷键可用于更轻松地导航“调用图”或“跟踪事件”。

使用调用图检查跟踪

调用图”提供方法跟踪或函数跟踪的图形表示,其中调用的周期和时间沿横轴表示,其被调用方沿纵轴显示。对系统 API 的调用显示为橙色,对您应用自身方法的调用显示为绿色,对第三方 API(包括 Java 语言 API)的调用显示为蓝色。图 4 显示了一个调用图示例,并阐释了给定方法或函数的自耗时间、子项耗时和总耗时概念。您可以在“使用自上而下和自下而上检查跟踪”部分中详细了解这些概念。

图 1. 调用图示例,其中显示了方法 D 的自耗时间、子项耗时和总耗时。

提示:要跳转到方法或函数的源代码,请右键点击它并选择跳转到源。此操作适用于“分析”窗格的任何标签页。

使用火焰图标签页检查跟踪

火焰图”标签页提供一个倒置的调用图,可聚合相同的调用堆栈。也就是说,共享相同调用方序列的相同方法或函数会被收集并在火焰图中表示为一条更长的条形(而不是像调用图那样显示为多条较短的条形)。这使得更容易看出哪些方法或函数消耗的时间最多。但是,这也意味着横轴不表示时间轴;相反,它表示每个方法或函数执行所需的相对时间量。

为了帮助阐明此概念,请参考图 2 中的调用图。请注意,方法 D 对 B 进行了多次调用(B1、B2 和 B3),其中一些对 B 的调用又对 C 进行了调用(C1 和 C3)。

图 2. 包含多个共享通用调用方序列的方法调用的调用图。

由于 B1、B2 和 B3 共享相同的调用方序列(A → D → B),因此它们被聚合,如图 3 所示。类似地,C1 和 C3 也被聚合,因为它们共享相同的调用方序列(A → D → B → C);请注意,C2 未包含在内,因为它具有不同的调用方序列(A → D → C)。

图 3. 聚合共享相同调用堆栈的相同方法。

聚合的调用用于创建火焰图,如图 4 所示。请注意,对于火焰图中的任何给定调用,消耗 CPU 时间最多的被调用方会首先出现。

图 4. 图 5 所示调用图的火焰图表示。

使用自上而下和自下而上检查跟踪

自上而下”标签页显示一个调用列表,其中展开方法或函数节点会显示其被调用方。图 5 显示了图 1 中调用图的自上而下关系图。图中的每个箭头都指向从调用方到被调用方。

如图 5 所示,展开“自上而下”标签页中方法 A 的节点会显示其被调用方,即方法 B 和 D。之后,展开方法 D 的节点会显示其被调用方,即方法 B 和 C,依此类推。与“火焰图”标签页类似,自上而下树会聚合共享相同调用堆栈的相同方法的跟踪信息。也就是说,“火焰图”标签页提供了“自上而下”标签页的图形表示。

自上而下”标签页提供以下信息来帮助描述每次调用所花费的 CPU 时间(时间也表示为所选范围内线程总时间的百分比)

  • 自耗时间:方法或函数调用执行自身代码而非其被调用方代码所花费的时间,如图 1 中方法 D 所示。
  • 子项耗时:方法或函数调用执行其被调用方代码而非自身代码所花费的时间,如图 1 中方法 D 所示。
  • 总耗时:方法的自耗时间子项耗时之和。这表示应用执行一次调用所花费的总时间,如图 1 中方法 D 所示。

图 5. 自上而下树。

图 6. 图 5 中方法 C 的自下而上树。

自下而上”标签页显示一个调用列表,其中展开函数或方法节点会显示其调用方。使用图 5 所示的跟踪示例,图 6 提供了方法 C 的自下而上树。在自下而上树中打开方法 C 的节点会显示其每个唯一的调用方,即方法 B 和 D。请注意,尽管 B 调用 C 两次,但在自下而上树中展开方法 C 的节点时,B 只出现一次。之后,展开 B 的节点会显示其调用方,即方法 A 和 D。

自下而上”标签页有助于按消耗最多(或最少)CPU 时间的方法或函数进行排序。您可以检查每个节点,以确定哪些调用方在调用这些方法或函数时花费了最多的 CPU 时间。与自上而下树相比,自下而上树中每个方法或函数的时间信息都参照每个树顶部的那个方法(顶部节点)。CPU 时间也表示为该录制期间线程总时间的百分比。下表有助于解释如何解读顶部节点及其调用方(子节点)的时间信息。

自耗时间 子项耗时 总耗时
自下而上树顶部的某个方法或函数(顶部节点) 表示方法或函数执行自身代码而非其被调用方代码所花费的总时间。与自上而下树相比,此时间信息表示在录制期间对该方法或函数的所有调用的总和。 表示方法或函数执行其被调用方代码而非自身代码所花费的总时间。与自上而下树相比,此时间信息表示在录制期间对该方法或函数被调用方的所有调用的总和。 自耗时间与子项耗时之和。
调用方(子节点) 表示被调用方在被调用方调用时所花费的总自耗时间。以图 6 中的自下而上树为例,方法 B 的自耗时间等于方法 C 每次被 B 调用时的自耗时间之和。 表示被调用方在被调用方调用时所花费的总子项耗时。以图 6 中的自下而上树为例,方法 B 的子项耗时等于方法 C 每次被 B 调用时的子项耗时之和。 自耗时间与子项耗时之和。

注意:对于给定的录制,当分析器达到文件大小限制时,Android Studio 会停止收集新数据(但这不会停止录制)。与采样跟踪相比,在执行插桩跟踪时,这种情况通常发生得更快,因为此类跟踪会在更短的时间内收集更多数据。如果您将检查时间扩展到达到限制后发生的录制时段,跟踪窗格中的时间数据不会改变(因为没有新数据可用)。此外,当您仅选择录制中没有可用数据的一部分时,跟踪窗格会显示 NaN 作为时间信息。

使用事件表检查跟踪

“事件”表列出了当前所选线程中的所有调用。您可以通过点击列标题进行排序。通过选择表中的一行,您可以将时间轴导航到所选调用的开始和结束时间。这使您能够准确地在时间轴上定位事件。

图 7. 在“分析”窗格中查看“事件”标签页。

检查调用堆栈帧

调用堆栈有助于了解代码的哪些部分已执行以及为何调用它。如果为 Java/Kotlin 程序收集了调用堆栈采样记录,调用堆栈通常不仅包括 Java/Kotlin 代码,还包括来自 JNI 本机代码、Java 虚拟机(例如 android::AndroidRuntime::start)和系统内核([kernel.kallsyms]+offset)的帧。这是因为 Java/Kotlin 程序通常通过 Java 虚拟机执行。需要本机代码来运行程序本身,并让程序与系统和硬件通信。分析器提供这些帧以实现精确性;但是,根据您的调查,您可能会发现或可能不会发现这些额外的调用帧有用。分析器提供了一种折叠您不感兴趣的帧的方法,以便您可以隐藏与您的调查无关的信息。

在下面的示例中,下面的跟踪中有许多标记为 [kernel.kallsyms]+offset 的帧,这些帧目前对开发没有用处。

Example call trace

要将这些帧折叠成一个,您可以从工具栏中选择“折叠帧”按钮,选择要折叠的路径,然后选择“应用”按钮以应用您的更改。在此示例中,路径为 [kernel.kallsyms]

Example of simpleperf menu

这样做会折叠左右面板上与所选路径对应的帧,如下所示。

Example of simpleperf collapsed frames

检查系统跟踪

检查系统跟踪时,您可以在“线程”时间轴中检查“跟踪事件”,以查看每个线程上发生的事件的详细信息。将鼠标指针悬停在事件上可查看事件名称以及在每个状态中花费的时间。点击事件可在“分析”窗格中查看更多信息。

检查系统跟踪:CPU 内核

除了 CPU 调度数据外,系统跟踪还包括按核心划分的 CPU 频率。这显示了每个核心上的活动量,并可能让您了解现代移动处理器中的哪些是“大”或“小”核心

图 8. 查看渲染线程的 CPU 活动和跟踪事件。

CPU 内核”窗格(如图 8 所示)显示了在每个核心上调度的线程活动。将鼠标指针悬停在线程活动上可查看该核心在特定时间正在运行哪个线程。

有关检查系统跟踪信息的更多信息,请参阅 systrace 文档的“调查 UI 性能问题”部分。

检查系统跟踪:帧渲染时间轴

您可以检查应用在主线程和 RenderThread 上渲染每帧所需的时间,以调查导致 UI 卡顿和帧速率低的瓶颈。要了解如何使用系统跟踪来调查和帮助减少 UI 卡顿,请参阅UI 卡顿检测

检查系统跟踪:进程内存 (RSS)

对于部署到运行 Android 9 或更高版本的设备上的应用,“进程内存 (RSS)”部分显示应用当前使用的物理内存量。

图 9. 在分析器中查看物理内存。

总耗时

这是您的进程当前使用的物理内存总量。在基于 Unix 的系统中,这被称为“驻留集大小 (Resident Set Size)”,是匿名分配、文件映射和共享内存分配所使用的所有内存的组合。

对于 Windows 开发者,驻留集大小类似于工作集大小。

已分配

此计数器跟踪进程的正常内存分配当前使用了多少物理内存。这些分配是匿名的(不由特定文件支持)和私有的(不共享)。在大多数应用中,它们由堆分配(使用 mallocnew)和栈内存组成。当从物理内存交换出去时,这些分配会写入系统交换文件。

文件映射

此计数器跟踪进程用于文件映射的物理内存量,即内存管理器将文件映射到内存区域的内存。

共享

此计数器跟踪此进程与系统中其他进程之间共享内存所使用的物理内存量。