CPU Profiler 可帮助您实时检查应用的 CPU 使用率和线程 Activity,并记录函数跟踪,以便您可以优化和调试您的应用代码。
为什么要分析 CPU 使用率
最大限度减少应用的 CPU 使用率具有许多优势,如提供更快更顺畅的用户体验,以及延长设备电池续航时间。 它还可帮助应用在各种新旧设备上保持良好性能。 与应用交互时,您可以使用 CPU Profiler 监控 CPU 使用率和线程 Activity。
对于应用进程中的每个线程,您可以查看一段时间内执行了哪些函数,以及在其执行期间每个函数消耗的 CPU 资源。 您还可以使用函数跟踪来识别调用方和被调用方。 调用方指调用其他函数的函数,而被调用方是指被其他函数调用的函数。 您可以使用此信息确定哪些函数负责调用常常会消耗大量特定资源的任务,并尝试优化应用代码以避免不必要的工作。
CPU Profiler 概览
当您打开 CPU Profiler 时,它将立即开始显示应用的 CPU 使用率和线程 Activity。 您应该会看到类似图 1 的一些内容:
图 1如图 1 所示,CPU Profiler 的默认视图包括以下内容:
- Event 时间线: 显示应用中在其生命周期不同状态间转换的 Activity,并表明用户与设备的交互,包括屏幕旋转 Event。
- CPU 时间线: 显示应用的实时 CPU 使用率(以占总可用 CPU 时间的百分比表示)以及应用使用的总线程数。 此时间线还显示其他进程的 CPU 使用率(如系统进程或其他应用),以便您可以将其与您的应用使用率进行对比。 通过沿时间线的水平轴移动鼠标,您还可以检查历史 CPU 使用率数据。
-
线程 Activity 时间线: 列出属于应用进程的每个线程并使用下面列出的颜色沿时间线标示它们的 Activity。 在您记录一个函数跟踪后,您可以从此时间线中选择一个线程以在跟踪窗格中检查其数据。
- 绿色: 表示线程处于活动状态或准备使用 CPU。 即,它正在“运行中”或处于“可运行”状态。
- 黄色: 表示线程处于活动状态,但它正在等待一个 I/O 操作(如磁盘或网络 I/O),然后才能完成它的工作。
- 灰色: 表示线程正在休眠且没有消耗任何 CPU 时间。 当线程需要访问尚不可用的资源时偶尔会发生这种情况。 线程进入自主休眠或内核将此线程置于休眠状态,直到所需的资源可用。
-
记录配置: 允许您选择以下选项之一以确定分析器记录函数跟踪的方式。
- Sampled: 一个默认配置,在应用执行期间频繁捕获应用的调用堆栈。 分析器比较捕获的数据集以推导与应用代码执行有关的时间和资源使用信息。 基于“Sampled”的跟踪的固有问题是,如果应用在捕获调用堆栈后进入一个函数并在下一次捕获前退出该函数,则分析器不会记录该函数调用。 如果您对此类生命周期很短的跟踪函数感兴趣,您应使用“Instrumented”跟踪。
- Instrumented: 一个默认配置,在运行时设置应用以在每个函数调用的开始和结束时记录时间戳。 它收集时间戳并进行比较,以生成函数跟踪数据,包括时间信息和 CPU 使用率。 请注意,与设置每个函数关联的开销会影响运行时性能,并可能会影响分析数据,对于生命周期相对较短的函数,这一点更为明显。 此外,如果应用短时间内执行大量函数,则分析器可能会迅速超出它的文件大小限制,且不能再记录更多跟踪数据。
- Edit configurations: 允许您更改上述“Sampled”和“Instrumented”记录配置的某些默认值,并将它们另存为自定义配置。 如需了解更多信息,请转到[创建记录配置]部分。
- 记录按钮: 用于开始和停止记录函数跟踪。 如需了解更多信息,请转到[记录和检查函数跟踪]部分 。
记录和检查函数跟踪
要开始记录函数跟踪,从下拉菜单中选择 Sampled 或 Instrumented 记录配置,或选择您创建的[自定义记录配置],然后点击 Record。
start recording与应用交互并在完成后点击 Stop recording。 stop
分析器将自动选择记录的时间范围,并在函数跟踪窗格中显示其跟踪信息,如图 2 所示。如果您想检查另一个线程的函数跟踪,只需从线程 Activity 时间线中选中它。
图 2.记录函数跟踪后的 CPU Profiler。- 1 选择时间范围: 用于确定您要在跟踪窗格中检查所记录时间范围的哪一部分。 当您首次记录函数跟踪时,CPU Profiler 将在 CPU 时间线中自动选择您的记录的完整长度。 如果您想仅检查所记录时间范围一小部分的函数跟踪数据,您可以点击并拖动突出显示的区域边缘以修改其长度。
- 2 时间戳: 用于表示所记录函数跟踪的开始和结束时间(相对于分析器从设备开始收集 CPU 使用率信息的时间)。 在选择时间范围时,您可以点击时间戳以自动选择完整记录,如果您有多个要进行切换的记录,则此做法尤其有用。
- 3 跟踪窗格: 用于显示您所选的时间范围和线程的函数跟踪数据。 仅在您至少记录一个函数跟踪后此窗格才会显示。 在此窗格中,您可以选择想如何查看每个堆叠追踪(使用跟踪标签),以及如何测量执行时间(使用时间引用下拉菜单)。
- 4 选择后,可通过 Top Down 树、Bottom Up 树、调用图表或火焰图的形式显示您的函数跟踪。 您可以在下文中了解每个跟踪窗格标签的更多信息。
- 5 从下拉菜单中选择以下选项之一,以确定如何测量每个函数调用的时间信息:
- Wall clock time:壁钟时间信息表示实际经过的时间。
- Thread time:线程时间信息表示实际经过的时间减去线程没有消耗 CPU 资源的任意时间部分。 对于任何给定函数,其线程时间始终少于或等于其壁钟时间。 使用线程时间可以让您更好地了解线程的实际 CPU 使用率中有多少是给定函数消耗的。
使用 Call Chart 标签检查跟踪
Call Chart 标签提供函数跟踪的图形表示形式,其中,水平轴表示函数调用(或调用方)的时间段和时间,并沿垂直轴显示其被调用者。 对系统 API 的函数调用显示为橙色,对应用自有函数的调用显示为绿色,对第三方 API(包括 Java 语言 API)的函数调用显示为蓝色。 下面的图 3 展示了一个调用图表示例,并描绘了给定函数的 self time、children time 以及总时间的概念。 您可以在如何使用 Top Down 和 Bottom Up 检查跟踪部分详细了解这些概念。
提示: 若要跳转到某个函数的源代码,请右键点击该函数并选择 Jump to Source。 这适用于任一跟踪窗格标签。
使用 Flame Chart 标签检查跟踪
Flame Chart 标签提供一个倒置的调用图表,其汇总相同的调用堆栈。 即,收集共享相同调用方顺序的完全相同的函数,并在火焰图中用一个较长的横条表示它们(而不是将它们显示为多个较短的横条,如调用图表中所示)。 这样更方便您查看哪些函数消耗最多时间。 不过,这也意味着水平轴不再代表时间线,相反,它表示每个函数相对的执行时间。
为帮助说明此概念,请考虑以下图 4 中的调用图表。 请注意,函数 D 多次调用 B(B1、B2 和 B3),其中一些对 B 的调用也调用了 C(C1 和 C3)。
图 4. 包含多个共享通用调用方顺序的函数调用的调用图表。
由于 B1、B2 和 B3 共享相同的调用方顺序 (A → D → B),因此,可将它们汇总在一起,如下所示。 同样,将 C1 和 C3 汇总在一起,因为它们也共享相同的调用方顺序 (A → D → B → C)—请注意,未包含 C2,因为它具有不同的调用方顺序 (A → D → C)。
图 5. 汇总共享相同调用堆栈的相同函数
汇总的函数调用用于创建火焰图,如图 6 所示。请注意,对于火焰图中任何给定的函数调用,消耗最多 CPU 时间的被调用方首先显示。
图 6. 图 4 中显示的调用图表的火焰图表示形式
使用 Top Down 和 Bottom Up 检查跟踪
Top Down 标签显示一个函数调用列表,在该列表中展开函数节点会显示函数的被调用方。 图 7 显示图 3 中调用图表的“Top Down”图表。图表中的每个箭头都从调用方指向被调用方。
如图 7 所示,在“Top Down”标签中展开函数 A 的节点可显示它的被调用方,即函数 B 和 D。 然后,展开函数 D 的节点可显示它的被调用方,即函数 B 和 C 等等。 与 Flame chart 标签相似,“Top Down”树汇总共享相同调用堆栈的相同函数的跟踪信息。 也就是说,Flame chart 标签可提供Top down 标签的图形化表示形式。
Top Down 标签提供以下信息以帮助说明在每个函数调用上所花费的 CPU 时间(时间也可以用线程总时间占所选时间范围的持续时间的百分比表示):
- Self: 表示函数调用在执行自己的代码(而非被调用方的代码)上所花的时间,如图 3 中的函数 D 所示。
- Children: 表示函数调用在执行自己的被调用方(而非自己的代码)上所花的时间,如图 3 中的函数 D 所示。
-
总和: 函数的 Self 和 Children 时间的总和。 这表示应用在执行函数调用上所花的总时间,如图 3 中函数 D 所示。
图7/8
Bottom Up 标签显示一个函数调用列表,在该列表中展开函数节点将显示函数的调用方。 通过使用图 7 中展示的跟踪示例,图 8 为函数 C 提供了一个“Bottom Up”树。 在“Bottom Up”树中打开函数 C 的节点可显示它独有的调用方,即函数 B 和 D。 请注意,尽管 B 调用 C 两次,但在“Bottom Up”树中展开函数 C 的节点时,B 仅显示一次。 然后,展开 B 的节点显示其调用方,即函数 A 和 D。
Bottom Up 标签用于按照消耗最多(最少)CPU 时间排序函数。 您可以检查每个节点以确定在调用函数上哪些调用方花了最多 CPU 时间。 与“Top Down”树相比,“Bottom Up”树中的每个函数的时间信息引用每个树顶部的函数(顶部模式)。 CPU 时间也可表示为在该记录期间占线程总时间的百分比。 下表有助于阐述如何解释顶部节点的时间信息及其调用方函数(子节点)。
Self | Children | 合计 | |
---|---|---|---|
“Bottom Up”树顶部的函数(顶部模式) | 表示函数在执行自己的代码(而非其被调用方的代码)上所花的时间。 与“Top Down”树相比,此时间信息表示在记录的持续时间内对此函数所有调用的总和。 | 表示函数执行它的被调用方(而非它自己的代码)上所花的总时间。 与“Top Down”树相比,此时间信息表示在记录的持续期间内所有对此函数被调用方的调用总和。 | self time 和 children time 的总和。 |
调用方函数(子节点) | 表示在由调用方调用时被调用方的总 self time。 以图 8 中的“Bottom Up”树为例,被 B 调用时,函数 B 的 self time 将等于函数 C 每个执行的 self time 的总和。 | 表示在由调用方调用时被调用方的总 children time。 以图 8 中的“Bottom Up”树为例,被 B 调用时,函数 B 的 children time 将等于函数 C 每个执行的 children time 的总和。 | self time 和 children time 的总和。 |
简单了解每个选项的功能后,我们针对上面的 demo 来实践下,这里我们选择 bottom up 选项,从上面图看出,每个函数所展示的信息有:Total(函数总共花费的时间)、%(所占的百分比)、Self(函数自身花费的时间)、%(所占的百分比)、Children(函数所调用的子函数花费的时间),%(所占百分比)。可以看到 main() run() 这些方法的时间主要都是花费在调用子函数上。那么我们先找出本身最耗时的方法,使用 Self 进行排序:
image.png
网友评论