Oracle® Solaris Studio 12.4:性能分析器教程

退出打印视图

更新时间: 2014 年 12 月
 
 

使用性能分析器检查 jlowfruit 数据

本节介绍如何了解通过 jlowfruit 样例代码创建的实验中的数据。

  1. 如果在上一节中创建的实验尚未打开,则可以从 jlowfruit 目录启动性能分析器并装入实验,如下所示:

    % analyzer test.1.er

    实验打开时,性能分析器将显示 "Overview"(概述)页面。

  2. 请注意,"Overview"(概述)页面显示度量值摘要并允许您选择度量。

    image:“Overview“(概述)屏幕显示 jlowfruit 程序的度量

    在此实验中,"Overview"(概述)显示大约 14% 的 "Total CPU Time"(CPU 总时间)(其是所有 "User CPU Time"(用户 CPU 时间)),加上大约 14% 的 "Sleep Time"(休眠时间)和 71% 的 "User Lock Time"(用户锁定时间)。用户 Java 代码 jlowfruit 是单线程的并且该线程是计算密集型的,但是所有 Java 程序使用多个线程,包括许多系统线程。这些线程的数量取决于选择的 JVM 选项,包括 "Garbage Collector"(垃圾收集器)参数以及运行程序的计算机的大小。

    实验记录在 Oracle Solaris 系统上,"Overview"(概述)显示记录的十二个度量,但是缺省情况下仅启用 "Total CPU Time"(CPU 总时间)。

    具有带颜色指示符的度量是由 Oracle Solaris 定义的十个微状态所用的时间。这些度量包括 "User CPU Time"(用户 CPU 时间)、"System CPU Time"(系统 CPU 时间)和 "Trap CPU Time"(自陷 CPU 时间)(合在一起等于 "Total CPU Time"(CPU 总时间)),以及各种等待时间。"Total Thread Time"(总线程时间)是所有微状态的总和。

    在 Linux 计算机上,仅记录 "Total CPU Time"(CPU 总时间),因为 Linux 不支持微状态记帐。

    缺省情况下,"Inclusive Total CPU Time"(包含总 CPU 时间)和 "Exclusive Total CPU Time"(独占总 CPU 时间)都处于选定状态。任何度量的包含都是指该函数或方法中的度量值,其中包括在所有函数或其所调用方法中累计的度量。独占仅指在该函数或方法内累计的度量。

  3. 单击 "Hot"(常用)按钮可以选择具有较高值的度量,从而在数据视图中显示这些度量。

    现在选中了 "Sleep Time"(休眠时间)和 "User Lock Time"(用户锁定时间)旁边的复选框。

    将更新底部的 "Metrics Preview"(度量预览)面板来显示在提供表格式数据的数据视图中显示度量的方式。接下来,您将查看哪些线程负责哪些度量。

  4. 现在,通过以下方法切换到 "Threads"(线程)视图:在 "Views"(视图)导航面板中单击线程名称,或者从菜单栏中选择 "Views"(视图)> "Threads"(线程)。

    image:性能分析器的 “Threads“(线程)视图

    具有几乎所有 "Total CPU Time"(CPU 总时间)的线程是 "Thread 2"(线程 2),其是此示例应用程序中的唯一用户 Java 线程。

    "Thread 15"(线程 15)最可能是用户线程,即使它实际上是由 JVM 在内部创建的。它仅在启动过程中处于活动状态并且积累了非常少的时间。在您的实验中,可能创建了与线程 15 相似的另一个线程。

    "Thread 1"(线程 1)在其整个时间内处于休眠状态。

    其余线程花费其时间等待锁,这是 JVM 在内部同步自身的方式。这些线程包括用于 HotSpot 编译和垃圾收集的那些线程。本教程不讲述 JVM 系统的行为,但在另一教程 Java 和混合 Java-C++ 分析中讲述。

  5. 返回 "Overview"(概述)屏幕并取消选中 "Sleep Time"(休眠时间)和 "User Lock Time"(用户锁定时间)的复选框。

  6. 单击 "Views"(视图)导航面板中的 "Functions"(函数)视图,或从菜单栏中选择 "Views"(视图)> "Functions"(函数)。

    image:“Functions“(函数)视图显示应用程序中函数的列表和每个函数的性能度量。

    "Functions"(函数)视图显示应用程序中函数的列表,以及每个函数的性能度量。列表最初按在每个函数中所用的 "Exclusive Total CPU Time"(独占总 CPU 时间)排序。"Functions"(函数)视图中还有许多来自 JVM 的函数,但是它们具有相对较低的度量。列表包括目标应用程序中的所有函数以及程序使用的任何共享对象。缺省情况下,最顶端的函数(即耗费资源最多的函数)处于选定状态。

    右侧的 "Selection Details"(选择详细信息)窗口显示选定函数的所有已记录度量。

    函数列表下的 "Called-by/Calls"(调用方/调用)面板提供有关选定函数的更多信息,并分为两个列表。"Called-by"(调用方)列表显示选定函数的调用方,度量值显示归属于其调用方的函数总度量。"Calls"(调用)列表显示选定函数的被调用方,并显示其被调用方的包含度量如何有助于选定函数的总度量。如果在 "Called-by/Calls"(调用方/调用)面板中双击任一列表中的函数,则在主 "Functions"(函数)视图中该函数将变为选定的函数。

  7. 通过选择各种函数进行实验,以了解 "Functions"(函数)视图中的窗口如何随选择的变化而更新。

    "Selection Details"(选择详细信息)窗口显示大多数的函数来自 jlowfruit 可执行文件,如 "Load Object"(装入对象)字段中所示。

    也可以通过单击列标题进行实验,以将排序从 "Exclusive Total CPU Time"(独占总 CPU 时间)更改为 "Inclusive Total CPU Time"(包含总 CPU 时间),或者按 "Name"(名称)排序。

  8. 在 "Functions"(函数)视图中,对初始化任务的两个版本 jlowfruit.init_bad()jlowfruit.init_good() 进行比较。

    可以看到,这两个函数具有大致相同的 "Exclusive Total CPU Time"(独占总 CPU 时间),但是包含时间截然不同。jlowfruit.init_bad() 函数的运行速度较慢,因为它在被调用方中花费了时间。这两个函数调用同一被调用方,但是它们在该例程中所用的时间截然不同。通过检查这两个例程的源代码,您可以找出原因。

  9. 选择函数 jlowfruit.init_good(),然后单击 "Source"(源)视图,或者从菜单栏中选择 "Views"(视图)> "Source"(源)。

  10. 调整窗口以便为代码提供更多空间:单击上边缘的向下箭头折叠 "Called-by/Calls"(调用方/调用)面板,单击侧边缘的向右箭头折叠 "Selection Details"(选择详细信息)面板。

    您应该向上滚动一点以便同时看到 jlowfruit.init_bad()jlowfruit.init_good() 的源代码。"Source"(源)视图看起来应该与以下屏幕抓图类似。

    image:“Source“(源)视图显示 jlowfruit.init_bad 和 jlowfruit.init_good 函数的代码以及每行的度量

    请注意,对 jlowfruit.init_static_routine() 的调用在 jlowfruit.init_good() 中的循环之外,而 jlowfruit.init_bad()jlowfruit.init_static_routine() 的调用则在循环之内。与好版本相比,差版本所用时间大约长十倍(与循环计数相对应)。

    此示例不像看起来的那样笨拙。它基于真实的代码,可生成一个表,其中每行都有一个图标。虽然很容易看出在此示例中初始化不应在循环之内,但是在真实的代码中,初始化嵌入在库例程中且不易看出。

    用于实现该代码的工具包提供两个库调用 (API)。第一个 API 将图标添加到表行,第二个 API 将图标向量添加到整个表。虽然使用第一个 API 更易于编码,但是每次添加图标时,工具包都要重新计算所有行的高度,以便为整个表设置正确的值。代码使用另一个 API 一次性添加所有图标时,高度的重新计算仅执行一次。

  11. 现在,返回到 "Functions"(函数)视图,并查看插入任务的两个版本 jlowfruit.insert_bad()jlowfruit.insert_good()

    请注意,"Exclusive Total CPU time"(独占总 CPU 时间)对 jlowfruit.insert_bad() 很重要,但是对 jlowfruit.insert_good() 则可以忽略。每个版本的包含时间和独占时间(表示被调用以将每个条目插入到列表中的函数 jlowfruit.insert_number() 中的时间)之间的差异是相同的。通过检查源代码,可以找出原因。

  12. 选择 jlowfruit.insert_bad() 并切换到 "Source"(源)视图:

    image:性能分析器中 jlowfruit.insert_bad() 的 “Source“(源)视图

    请注意时间(不包括对 jlowfruit.insert_number() 的调用)花费在循环查找中,通过线性搜索找到正确的位置以插入新数字。

  13. 现在向下滚动以查看 jlowfruit.insert_good()

    image:性能分析器中 jlowfruit.insert_good() 的 “Source“(源)视图

    请注意代码更为复杂,因为它执行二进制搜索,以查找要插入的正确位置,但是所用的总时间(不包括对 jlowfruit.insert_number() 的调用)比 jlowfruit.insert_bad() 中少得多。此示例说明二进制搜索的效率比线性搜索更高。

    您也可以在 "Timeline"(时间线)视图中以图形方式查看例程中的差异。

  14. 单击 "Timeline"(时间线)视图,或者从菜单栏中选择 "Views"(视图)> "Timeline"(时间线)。

    会将分析数据记录为一系列事件,每个事件表示每个线程的分析时钟的每一计时周期。"Timeline"(时间线)视图显示各个事件以及该事件中记录的调用堆栈。调用堆栈以调用堆栈中的帧列表形式显示,其中叶 PC(在事件发生的瞬间随即执行的指令)处于顶部,其次为调用它的调用点,等等。对于程序的主线程,调用堆栈的顶部始终为 _start

  15. 在 "Timeline"(时间线)工具栏中,单击 "Call Stack Function Colors"(调用堆栈函数颜色)图标以对函数着色,或者从菜单栏中选择 "Tools"(工具)> "Function Colors"(函数颜色),将看到如下所示的对话框。

    image:性能分析器的 “Timeline“(时间线)视图,其中打开了 “Function Colors“(函数颜色)对话框

    函数颜色已更改,以便为屏幕抓图更清晰地区分函数的好版本和差版本。jlowfruit.init_bad()jlowfruit.insert_bad() 函数现在都呈红色,而 jlowfruit.init_good()jlowfruit.insert_good() 现在都呈亮绿色。

  16. 要使 "Timeline"(时间线)视图的外观类似,请在 "Function Colors"(函数颜色)对话框中执行以下操作:

    • 向下滚动 "Legend"(图例)中 java 方法的列表以查找 jlowfruit.init_bad() 方法。

    • 选择 jlowfruit.init_bad() 方法,单击 "Swatches"(色板)中的红色方块,然后单击 "Set Selected Functions"(设置所选函数)按钮。

    • 选择 jlowfruit.insert_bad() 方法,单击 "Swatches"(色板)中的红色方块,然后单击 "Set Selected Functions"(设置所选函数)按钮。

    • 选择 jlowfruit.init_good() 方法,单击 "Swatches"(色板)中的绿色方块,然后单击 "Set Selected Functions"(设置所选函数)按钮。

    • 选择 jlowfruit.insert_good() 方法,单击 "Swatches"(色板)中的绿色方块,然后单击 "Set Selected Functions"(设置所选函数)按钮。

  17. 查看时间线的顶部条。

    时间线的顶部条是 "CPU Utilization Samples"(CPU 利用率抽样)条,将鼠标光标移动到第一列上时,可以在工具提示中看到它。"CPU Utilization Samples"(CPU 利用率抽样)条的每个段表示一秒的间隔,显示在该秒执行期间目标的资源使用率。

    在此示例中,段大部分为灰色并有一些绿色,反映出仅一小部分 "Total Time"(总时间)用于积累 "User CPU Time"(用户 CPU 时间)。"Selection Details"(选择详细信息)窗口显示颜色到微状态的映射,尽管它在屏幕抓图中不可见。

  18. 查看时间线的第二个条。

    第二个条是 "Clock Profiling Call Stacks"(时钟分析调用堆栈)条,标记有 "1 T:2",这表示进程 1 和线程 2(示例中的主用户线程)。"Clock Profiling Call Stacks"(时钟分析调用堆栈)条显示程序执行期间发生的事件的两个数据条。靠上的条显示调用堆栈的颜色编码表示形式,靠下的条显示每个事件的线程状态。此示例中的状态始终为 "User CPU Time"(用户 CPU 时间),因此它看起来是一条纯绿色的线。

    您应该会看到标记有不同线程编号的一个或两个其他条,但是它们在运行开始时将仅具有几个事件。

    如果单击该 "Clock Profiling Call Stacks"(时钟分析调用堆栈)条中的任何位置,则选择最近的事件,并在 "Selection Details"(选择详细信息)窗口中显示该事件的详细信息。从调用堆栈的图案中,可以看到在屏幕抓图中以亮绿色显示的 jlowfruit.init_good()jlowfruit.insert_good() 例程中的时间比以红色显示的 jlowfruit.init_bad()jlowfruit.insert_bad() 例程中的对应时间要短得多。

  19. 选择与时间线中的好例程和差例程相对应的区域中的事件,并在 "Selection Details"(选择详细信息)窗口下的 "Call Stack - Timeline"(调用堆栈 - 时间线)窗口中查看调用堆栈。

    可以在 "Call Stack"(调用堆栈)窗口中选择任何帧,然后在 "Views"(视图)导航栏上选择 "Source"(源)视图,并转到该源代码行的源代码。也可以双击调用堆栈中的帧以转到 "Source"(源)视图,或者右键单击调用堆栈中的帧并从弹出菜单中进行选择。

  20. 通过时间线顶部的滑块、使用 + 键或者通过用鼠标双击,均可以放大事件。

    如果放大得足够大,则可以看到显示的数据不是连续的,而是由离散的事件组成,每个分析计时周期(在此示例中约为 10 ms)都有一个事件。

    image:性能分析器中的 “Timeline“(时间线)视图的放大版本

    按 F1 键可查看帮助以了解有关 "Timeline"(时间线)视图的更多信息。

  21. 单击 "Call Tree"(调用树)视图或者选择 "Views"(视图)> "Call Tree"(调用树),以查看程序的结构。

    "Call Tree"(调用树)视图显示程序的动态调用图,注释有性能信息。

    image:“Call Tree“(调用树)视图显示程序的动态调用图,注释有性能度量