Sun Studio 12:性能分析器

第 8 章 了解带注释的源代码和反汇编数据

带注释的源代码和带注释的反汇编代码可用于确定函数中哪些源代码行或指令造成性能低下,同时也可用于查看有关编译器如何在代码上执行转换的注释。本节介绍了注释过程,以及在解释带注释的代码时涉及的一些问题。

带注释的源代码

可在性能分析器中查看实验的带注释的源代码,方法是选择分析器窗口左窗格中的“源”标签。另外,可以使用 er_src 实用程序,在不运行实验的情况下查看带注释的源代码。本手册的此部分介绍了源代码如何在性能分析器中显示。有关使用 er_src 实用程序查看带注释的源代码的详细信息,请参见在不运行实验的情况下查看源代码/反汇编代码

分析器中的带注释的源代码包含以下信息:

性能分析器“源”标签布局

“源”标签分为若干列,其中左侧固定宽度的几列显示各个度量,右侧的其余部分显示带注释的源代码。

标识初始源代码行

在带注释的源代码中,所有以黑色显示的行都来自于初始源文件。在带注释的源代码列中,每行开头的数字与初始源文件中的行号对应。以不同颜色显示其中字符的行可能是索引行,也可能是编译器注释行。

“源”标签中的索引行

源文件是指可编译生成目标文件或解释为字节代码的任何文件。目标文件通常包含与源代码中的函数、子例程或方法对应的一个或多个可执行代码区域。分析器分析目标文件,标识每个作为函数的可执行区域,并尝试将其在目标代码中找到的函数映射至与目标代码相关联的源文件中的函数、例程、子例程或方法。当分析器操作成功后,它将在带注释的源文件中对应于在目标代码中找到的函数的第一条指令处添加一个索引行。

带注释的源代码会为每个函数(包括内联函数,即使内联函数没有在“函数”标签中的列表中显示)显示一个索引行。在“源”标签中以红色斜体显示索引行,且索引行中的文本括在尖括号中。类型最简单的索引行与函数的缺省上下文对应。任何函数的缺省源上下文都被定义为该函数的第一条指令所归属的源文件。以下示例显示了 C 函数 icputime 的索引行。


0.       0.         600. int
0.       0.         601. icputime(int k)
0.       0.         602. {
0.       0.              <Function: icputime>

从上面的示例能够看出,索引行紧跟在第一条指令行的后面。对于 C 源代码,第一条指令对应于函数体开头的开型花括号处。在 Fortran 源代码中,各个子例程的索引行跟在包含 subroutine 关键字的行后面。同样,main 函数索引行跟在应用程序启动时执行的第一个 Fortran 源代码指令的后面,如以下示例所示:


0.       0.       1. ! Copyright 02/04/2000 Sun Microsystems, Inc. All Rights Reserved
0.       0.       2. !
0.       0.       3. ! Synthetic f90 program, used for testing openmp directives and 
0.       0.       4. ! the analyzer
0.       0.       5.
0.       0.       6. !$PRAGMA C (gethrtime, gethrvtime)
0.       0.       7.
0.       81.497    [  8] 9000    format(’X ’, f7.3, 7x, f7.3, 4x, a)
0.       0.             <Function: main>

有时,分析器可能无法将它在目标代码中找到的函数映射至与该目标代码相关联的源文件中的任何编程指令;例如,可能从另一个文件(如某个头文件)执行代码 #included 操作或内联操作。

如果目标代码的源代码不是目标代码中包含的函数的缺省源上下文,那么对应于目标代码的带注释的源代码将包含一个特殊索引行,该索引行交叉引用该函数的缺省源上下文。例如,编译 synprog 演示程序将创建对应于源文件 endcases.c 的目标模块 endcases.oendcases.c 中的源代码声明函数 inc_func,该函数在头文件 inc_func.h 中定义。头文件通常以这种方式包含内联函数定义。由于 endcases.c 中声明函数 inc_func,但 endcases.c 中没有源代码行与 inc_func 的指令对应,因此 endcases.c 的带注释的源文件的顶部会显示一个特殊的索引行,如下所示:


0.650     0.650       <Function: inc_func, instructions from source file inc_func.h>

该索引行上的度量指示来自 endcases.o 目标模块的部分代码(在源文件 endcases.c 中)不具有行映射。

此外,分析器还会在带注释的源代码中为定义了 inc_func 函数的 inc_func.h 添加一个标准索引行。

.


0.       0.         2.
0.       0.         3. void
0.       0.         4. inc_func(int n)
0.       0.         5. {
0.       0.            <Function: inc_func>

类似地,如果函数具有替代源上下文,则会在缺省源上下文的带注释的源代码中显示交叉引用该上下文的索引行。


0.            0.        142. inc_body(int n)
0.650     0.650       <Function: inc_body, instructions from source file inc_body.h>
0.            0.        143. {
0.            0.                  <Function: inc_body>
0.            0.        144. #include "inc_body.h"
0.            0.        145. }

双击引用另一个源上下文的索引行,将在源代码窗口中显示该源文件的内容。

此外,以红色显示的行还有一些特殊索引行,以及其他一些不是编译器注释的特殊行。例如,由于编译器优化的原因,可能会为目标代码中的某个函数创建一个特殊索引行,但该索引行并不对应在任何源文件中编写的代码。有关详细信息,请参阅“源”、“反汇编”和 "PC" 标签中的特殊行

编译器注释

编译器注释指示如何生成编译器优化代码。为了将编译器注释行同索引行及初始源代码行区分开,以蓝色显示编译器注释行。编译器的各个部分可将注释合并到可执行文件中。每个注释都与源代码的特定行相关联。当写入带注释的源代码后,任何源代码行的编译器注释会直接显示在源代码行的前面。

编译器注释描述为了优化源代码而对其进行的大量转换。这些转换包括循环优化、并行化、内联和流水线作业。以下显示了一个编译器注释的示例。


                    Function freegraph inlined from source file ptraliasstr.c into 
                    the code for the following line
0.       0.         47.       freegraph();
                    48.    }
0.       0.         49.    for (j=0;j<ITER;j++) {

                    Function initgraph inlined from source file ptraliasstr.c into 
                    the code for the following line
0.       0.         50.       initgraph(rows);
        
                    Function setvalsmod inlined from source file ptraliasstr.c into 
                    the code for the following line
                    Loop below fissioned into 2 loops
                    Loop below fused with loop on line 51
                    Loop below had iterations peeled off for better unrolling and/or 
                    parallelization
                    Loop below scheduled with steady-state cycle count = 3
                    Loop below unrolled 8 times
                    Loop below has 0 loads, 3 stores, 3 prefetches, 0 FPadds, 
                    0 FPmuls, and FPdivs per iteration
                    51.       setvalsmod();

请注意,第 51 行的注释包括循环注释,因为函数 setvalsmod() 包含循环代码,并且函数已内联。

可以使用“设置数据表示”对话框中的“源码/反汇编”标签来设置在“源”标签中显示的编译器注释类型;有关详细信息,请参见设置数据表示选项

通用子表达式删除

一种最常见的优化是可以识别在多个位置出现的同一个表达式,并可通过在一个位置生成该表达式的代码来提高性能。例如,如果在一个代码块的 ifelse 分支同时出现了相同的操作,则编译器可以仅将该操作移到 if 语句前。同时,编译器将基于前面某次出现的该表达式为指令分配行号。如果分配给通用代码的行号与 if 结构的一个分支对应,并且该代码实际上始终包含另一个分支,则带注释的源代码会在不包含的分支内的行上显示度量。

循环优化

编译器可以执行多种类型的循环优化。部分比较常用的优化如下:

循环展开是指在循环体中重复多次循环迭代,并相应调整循环索引的过程。随着循环体的增大,编译器能够更加有效地调度指令,同时降低由于循环索引递增和条件检查操作而引起的开销。循环的剩余部分则使用循环剥离进行处理。

循环剥离是指从循环中删除一定数量的循环迭代,并适当将它们移动至循环的前面或后面的过程。

循环交换可更改嵌套循环的顺序,以便最大程度地降低内存跨距 (memory stride),最大程度地提高高速缓存行命中率。

循环合并是指将相邻或位置接近的循环合并为一个循环的过程。循环合并的优点与循环展开类似。此外,如果在两个预优化的循环中访问通用数据,则循环合并可以改进高速缓存定位 (cache locality),从而为编译器提供更多利用指令级并行性的机会。

循环分裂与循环合并正好相反:它将一个循环分裂为两个或更多的循环。如果循环中的计算量过于庞大,导致寄存器溢出进而造成性能降级,则有必要采用这种优化。如果一个循环中包含多个条件语句,也可以使用循环分裂。有时可以将循环分成两类:带条件语句的和不带条件语句的。这可以提高不带条件语句的循环中软件流水线作业的机会。

有时,对于嵌套式循环,编译器会先使用循环分裂来拆分循环,然后执行循环合并,以不同的方式将循环重新组合,以达到提高性能的目的。在这种情况下,您可以看到与以下注释类似的编译器注释:


    Loop below fissioned into 2 loops
    Loop below fused with loop on line 116
    [116]    for (i=0;i<nvtxs;i++) {

函数内联

利用内联函数,编译器可将函数指令直接插在该函数的调用位置处,而不必执行真正的函数调用。这样,与 C/C++ 宏类似,内联函数的指令会在每个调用位置进行复制。编译器在高优化级别(4 和 5)执行显式内联或自动内联。内联可以节约函数调用方面的开销,并提供可以优化寄存器使用和指令调度的更多指令,但代价是代码会占用内存中较多的资源。以下为内联编译器注释的一个示例。


                Function initgraph inlined from source file ptralias.c 
                    into the code for the following line
0.       0.         44.       initgraph(rows);

注 –

在分析器的“源”标签中,编译器注释不会换行,即不会显示在两行上。


并行化

如果您的代码包含 Sun、Cray 或 OpenMP 并行化指令,则可经过编译在多个处理器上并行执行。编译器注释会指示执行过并行化操作和尚未执行并行化操作的位置,以及相应的原因。以下是一个有关并行化操作的计算机注释示例。


0.       6.324       9. c$omp  parallel do shared(a,b,c,n) private(i,j,k)
0.       0.    
                   Loop below parallelized by explicit user directive
                   Loop below interchanged with loop on line 12
0.010    0.010     [10]            do i = 2, n-1

                   Loop below not parallelized because it was nested in a parallel loop
                   Loop below interchanged with loop on line 12
0.170    0.170      11.               do j = 2, i

有关并行执行和编译器生成的主体函数的更多详细信息,请参阅OpenMP 软件执行概述

带注释的源代码中的特殊行

在“源”标签中,还可以看到有关某些特殊情况的另外几种注释,它们或者显示为编译器注释,或者显示为与索引行颜色相同的特殊行。有关详细信息,请参阅“源”、“反汇编”和 "PC" 标签中的特殊行

源代码行度量

每行可执行代码的源代码度量都会在固定宽度的几列中显示出来。这些度量与函数列表中的度量相同。您可以使用 .er.rc 文件更改实验的缺省值;有关详细信息,请参见设置缺省值的命令。您还可以在分析器中使用“设置数据表示”对话框更改显示的度量和突出显示的阈值;有关详细信息,请参见设置数据表示选项

带注释的代码在源代码行级别显示应用程序的度量。该度量是通过使用记录在应用程序调用栈中的 PC(program count,程序计数),并将每个 PC 映射至源代码行的方式生成的。要生成带注释的源文件,分析器首先确定在特定目标模块(.o 文件)或装入对象中生成的所有函数,然后从每个函数扫描所有 PC 的数据。为了生成带注释的源代码,分析器必须能够找到并读取目标模块或装入对象来确定从 PC 至源代码行的映射,此外,它还必须能够读取源文件来生成要显示的带注释的副本。分析器依次在以下缺省位置搜索源文件、目标文件和可执行文件,并在找到具有正确基本名称的文件时停止:

缺省位置可以使用 addpathsetpath 指令更改,也可以使用分析器 GUI 更改。

如果使用由 addpath setpath 设置的路径列表找不到文件,则可以使用 pathmap 命令指定一个或多个路径重映射。使用 pathmap 命令,可以指定 old-prefixnew-prefix。在以 old-prefix 所指定的前缀开头的源文件、目标文件或共享对象的任何路径名中,旧的前缀将由 new-prefix 所指定的新前缀替代。然后,使用所得到的路径查找文件。可采用多个 pathmap 命令,并逐一尝试,直到找到文件为止。

编译过程要经过很多阶段,这取决于请求的优化级别,并且会发生转换,该转换会使指令到源代码行的映射变得混乱。对于某些优化,源代码行信息可能完全丢失,而对于其他优化,源代码行信息则可能变得混乱。编译器依赖各种试探操作来跟踪指令的源代码行,而这些试探操作不是绝对无误的。

解释源代码行度量

指令的度量必须解释为在等待执行指令时累积的度量。如果记录事件时执行的指令来自与叶 PC 相同的源代码行,则度量可以解释为由于执行了该源代码行的缘故。但是,如果叶 PC 与所执行的指令来自不同的源代码行,那么叶 PC 所属源代码行的度量中至少有一些度量必须解释为在等待执行此代码行时累积的度量。例如,当一个源代码行上计算的值被用在下一个源代码行上时。

当执行过程中存在严重的延迟(如高速缓存未命中或资源队列停止)或指令等待前一个指令返回结果时,如何解释度量的问题是最重要的问题。在这些情况下,源代码行的度量看上去很高(高得不太合理),应该查看代码中的其他行以找出导致高度量值的行。

度量格式

表 8–1 中说明了可能出现在带注释的源代码行上的四种度量格式。

表 8–1 带注释的源代码度量

度量 

含义 

(空白) 

程序中没有 PC 对应于该代码行。此情形应该始终适用于注释行,并且在下列情况下适用于出现的代码行: 

  • 所出现的代码段的所有指令已在优化期间删除。

  • 代码在其他地方重复,并且编译器执行通用子表达式识别并标记带有其他副本代码行的所有指令。

  • 编译器用不正确的行号来标记该行的指令。

0.

程序中的某些 PC 被标记为从该代码行派生,但没有数据引用这些 PC:它们从不会位于被抽样统计或被跟踪的调用栈中。0. 度量不表示该行不执行,只是表示该行不以统计方式显示在分析数据包或记录的跟踪数据包中。

0.000

该行至少有一个 PC 出现在数据中,但是计算的度量值舍入为零。 

1.234

归属于该行的所有 PC 的度量总计达到了显示的非零数值。 

带注释的反汇编代码

带注释的反汇编代码提供了函数或目标模块指令的汇编代码列表,以及与每个指令相关联的性能度量。带注释的反汇编代码有多种显示方法,具体取决于行号映射和源文件是否可用,以及请求带注释的反汇编代码的函数的目标模块是否已知:

反汇编代码中的每个指令都用以下信息进行注释。

调用地址尽可能解析为符号(如函数名称)。度量显示在指令行上,如果设置了相应的首选项,度量还可显示在任何交错显示的源代码中。可能的度量值与前面介绍的源代码注释的度量值一样(请参见表 8–1)。

在多个位置执行 #included 的代码的反汇编代码列表将在每次代码执行 #included 时重复一次反汇编指令。只有重复的反汇编代码块首次显示在某个文件中时,源代码才会交错显示。例如,如果在名为 inc_body.h 的头文件中定义的代码块分别由 inc_body inc_entryinc_middleinc_exit 四个函数 #included,则反汇编指令块将在 inc_body.h 的反汇编代码列表中出现四次,但是源代码仅在四个反汇编指令块的第一个块中交错显示。切换到“源”标签,可以看到与每次重复的反汇编代码对应的索引行。

索引行可以显示在“反汇编”标签中。与“源”标签不同的是,不能直接使用这些索引行进行导航。不过,将光标放在紧挨该索引行下方的其中一条指令上,然后选择“源”标签,可以导航至该索引行中引用的文件。

对其他文件中的代码执行了 #include 操作的文件会将通过该操作包含的代码显示为不与源代码交错显示的原始反汇编指令。不过,将光标放在这些指令中的其中一条指令上,然后选择“源”标签,将打开包含 #included 代码的文件。在显示此文件时选择“反汇编”标签,将显示与源代码交错显示的反汇编代码。

内联函数的源代码可以与反汇编代码交错显示,但宏的源代码则不能与反汇编代码交错显示。

代码未优化时,每个指令的行号按顺序显示,源代码行和反汇编指令的交错显示以预期的方式进行。优化后,后面行的指令有时会显示在前面行的指令前面。分析器的交错显示算法为:只要指令显示为来自第 N 行,该行前所有源代码行(包括第 N 行)都会写入该指令前。优化的一个作用是源代码可以在控制转移指令与其延迟槽指令之间显示。与源代码的第 N 行关联的编译器注释就写在该行之前。

解释带注释的反汇编代码

解释带注释的反汇编代码并不是一件简单的工作。叶 PC 是要执行的下一条指令的地址,因此归属到某个指令的度量应该视为等待执行该指令所用的时间。不过,指令的执行并不总是按顺序发生,而且在记录调用栈时可能存在延迟。要利用带注释的反汇编代码,应该熟悉记录实验的硬件以及装入和执行指令的方式。

接下来的几个小节将讨论有关解释带注释的反汇编代码的一些问题。

指令发送分组

指令按组装入和发送(称为指令发送组)。组中包括哪些指令取决于硬件、指令类型、已执行的指令以及与其他指令或寄存器的各种相关性。因此,可能不会单独发送某些指令,因为它们始终与前一条指令在同一个时钟周期内发送,所以它们从不单独表示下一条要执行的指令。而且,在记录调用栈时,可能有多条指令被视为是下一条要执行的指令。

指令发送规则因处理器类型的不同而不同,并且还取决于高速缓存行内的指令对齐。由于链接程序比高速缓存行要求执行更精确的指令对齐,因此在看上去不相关的函数中进行的更改可能会导致指令的对齐方式不同。不同的对齐方式会引起性能的改进或降级。

下面列举了一种假设情况,即同一个函数在略微不同的环境下进行编译和链接的情况。以下两个输出示例均为 er_print 实用程序的带注释的反汇编代码列表。 两个示例中的指令是相同的,但指令的对齐方式不同。

在以下示例中,指令的对齐方式是将 cmpbl,a 两条指令映射到不同的高速缓存行,并且大量的时间都用于等待执行这两条指令。


   Excl.     Incl.
User CPU  User CPU
    sec.      sec.
                             1. static int
                             2. ifunc()
                             3. {
                             4.     int i;
                             5.
                             6.     for (i=0; i<10000; i++)
                                <function: ifunc>
   0.010     0.010              [ 6]    1066c:  clr         %o0
   0.        0.                 [ 6]    10670:  sethi       %hi(0x2400), %o5
   0.        0.                 [ 6]    10674:  inc         784, %o5
                             7.         i++;
   0.        0.                 [ 7]    10678:  inc         2, %o0
## 1.360     1.360              [ 7]    1067c:  cmp         %o0, %o5
## 1.510     1.510              [ 7]    10680:  bl,a        0x1067c
   0.        0.                 [ 7]    10684:  inc         2, %o0
   0.        0.                 [ 7]    10688:  retl
   0.        0.                 [ 7]    1068c:  nop
                             8.     return i;
                             9. }

在以下示例中,指令的对齐方式是将 cmpbl,a 两条指令映射到相同的高速缓存行,并且大量的时间都用于等待执行其中一条指令。


   Excl.     Incl.
User CPU  User CPU
    sec.      sec.
                             1. static int
                             2. ifunc()
                             3. {
                             4.     int i;
                             5.
                             6.     for (i=0; i<10000; i++)
                                <function: ifunc>
   0.        0.                 [ 6]    10684:  clr         %o0
   0.        0.                 [ 6]    10688:  sethi       %hi(0x2400), %o5
   0.        0.                 [ 6]    1068c:  inc         784, %o5
                             7.         i++;
   0.        0.                 [ 7]    10690:  inc         2, %o0
## 1.440     1.440              [ 7]    10694:  cmp         %o0, %o5
   0.        0.                 [ 7]    10698:  bl,a        0x10694
   0.        0.                 [ 7]    1069c:  inc         2, %o0
   0.        0.                 [ 7]    106a0:  retl
   0.        0.                 [ 7]    106a4:  nop
                             8.     return i;
                             9. }

指令发送延迟

有时,会更频繁地出现特定的叶 PC,因为这些 PC 表示的指令在发送前被延迟。导致出现这种情况的原因有多种,以下列出了其中的一些原因:

硬件计数器溢出的归属

除 TLB 未命中外,硬件计数器溢出事件的调用栈按指令的顺序记录在后续的某些点上,而不是记录在发生溢出的点上,原因之一是处理由溢出产生的中断所用的时间。对于某些计数器(如发送的周期或指令),这种延迟无关紧要。对于其他计数器(例如,那些计数高速缓存未命中或浮点运算的计数器),度量被归属到导致溢出的指令之外的其他指令。通常,引起事件的 PC 只是记录的 PC 前的几条指令,而这些指令可以在反汇编代码列表中正确定位。但是,如果该指令范围内存在分支目标,那么要分辨哪条指令对应于引起事件的 PC 是很困难的,甚至是不可能的。对于计数内存访问事件的硬件计数器,如果该计数器名称带有加号 + 前缀,则收集器会搜索引起事件的 PC。

“源”、“反汇编”和 "PC" 标签中的特殊行

外联函数

外联函数可以在反馈优化编译期间创建。在“源”标签和“反汇编”标签中,外联函数显示为特殊的索引行。在“源”标签中,在已转换为外联函数的代码块中会显示一条注释。


                   Function binsearchmod inlined from source file ptralias2.c into the 
0.       0 .         58.         if( binsearchmod( asize, &element ) ) {
0.240    0.240       59.             if( key != (element << 1) ) {
0.       0.          60.                 error |= BINSEARCHMODPOSTESTFAILED;
                        <Function: main -- outline code from line 60 [_$o1B60.main]>
0.040    0.040     [ 61]                 break;
0.       0.          62.             }
0.       0.          63.         }

在“反汇编”标签中,外联函数通常在文件结尾处显示。


                        <Function: main -- outline code from line 85 [_$o1D85.main]>
0.       0.             [ 85] 100001034:  sethi       %hi(0x100000), %i5
0.       0.             [ 86] 100001038:  bset        4, %i3
0.       0.             [ 85] 10000103c:  or          %i5, 1, %l7
0.       0.             [ 85] 100001040:  sllx        %l7, 12, %l5
0.       0.             [ 85] 100001044:  call        printf ! 0x100101300
0.       0.             [ 85] 100001048:  add         %l5, 336, %o0
0.       0.             [ 90] 10000104c:  cmp         %i3, 0
0.       0.             [ 20] 100001050:  ba,a        0x1000010b4
                        <Function: main -- outline code from line 46 [_$o1A46.main]>
0.       0.             [ 46] 100001054:  mov         1, %i3
0.       0.             [ 47] 100001058:  ba          0x100001090
0.       0.             [ 56] 10000105c:  clr         [%i2]
                        <Function: main -- outline code from line 60 [_$o1B60.main]>
0.       0.             [ 60] 100001060:  bset        2, %i3
0.       0.             [ 61] 100001064:  ba          0x10000109c
0.       0.             [ 74] 100001068:  mov         1, %o3

外联函数的名称显示在方括号中,并对外联代码段的有关信息进行编码,这些信息包括从中提取代码的函数名称以及源代码中该段的起始行号。不同的发行版可以具有不同的重整名称 (mangled name)。分析器提供了函数名称的可读版本。有关详细信息,请参阅外联函数

如果在收集应用程序的性能数据时调用了外联函数,则分析器会在带注释的反汇编代码中显示一个特殊行,以显示该函数的包含度量。有关详细信息,请参见包含度量

编译器生成的主体函数

编译器并行化函数中的循环或具有并行化指令的区域时,将会创建初始源代码中不存在的新主体函数。OpenMP 软件执行概述中介绍了这些函数。

编译器将重整名称 (mangled name) 分配给主体函数,这些主体函数对并行结构的类型、从中提取结构的函数名称、初始源代码中该结构的起始行号以及并行结构的序列号进行了编码。这些重整名称 (mangled name) 因微任务化库的发行版而异,但均会取消重整 (demangle) 为更易于理解的名称。

以下显示的是函数列表中显示的一个典型的由编译器生成的主体函数。


7.415      14.860      psec_ -- OMP sections from line 9 [_$s1A9.psec_]
3.873       3.903      craydo_ -- MP doall from line 10 [_$d1A10.craydo_]

从上面的示例可以看出,最先显示的是从中提取结构的函数名称,接着是并行结构的类型,然后是并行结构的行号,最后在方括号中显示的是由编译器生成的主体函数的重整名称 (mangled name)。类似地,在反汇编代码中,也会生成一个特殊索引行。


0.       0.            <Function: psec_ -- OMP sections from line 9 [_$s1A9.psec_]>
0.       7.445         [24]    1d8cc:  save        %sp, -168, %sp
0.       0.            [24]    1d8d0:  ld          [%i0], %g1
0.       0.            [24]    1d8d4:  tst         %i1

0.       0.            <Function: craydo_ -- MP doall from line 10 [_$d1A10.craydo_]>
0.       0.030         [ ?]    197e8:  save        %sp, -128, %sp
0.       0.            [ ?]    197ec:  ld          [%i0 + 20], %i5
0.       0.            [ ?]    197f0:  st          %i1, [%sp + 112]
0.       0.            [ ?]    197f4:  ld          [%i5], %i3

对于 Cray 指令,函数可能与源代码行号不相关。在这种情况下,会在行号的位置显示 [ ?]。如果索引行显示在带注释的源代码中,则该索引行指示不带行号的指令,如下所示。


                     9. c$mic  doall shared(a,b,c,n) private(i,j,k)
                  
                   Loop below fused with loop on line 23
                   Loop below not parallelized because autoparallelization 
                     is not enabled
                   Loop below autoparallelized
                   Loop below interchanged with loop on line 12
                   Loop below interchanged with loop on line 12
3.873     3.903         <Function: craydo_ -- MP doall from line 10 [_$d1A10.craydo_],
                      instructions without line numbers>
0.        3.903     10.            do i = 2, n-1

注 –

在实际显示中,索引行和编译器注释行并不换行。


动态编译的函数

动态编译的函数是指程序执行时编译和链接的函数。收集器中并没有有关采用 C 或 C++ 编写的动态编译函数的信息,除非用户使用收集器 API 函数 collector_func_load() 提供所需的信息。“函数”标签、“源”标签和“反汇编”标签中显示的信息取决于传递给 collector_func_load() 的信息,如下所示:

有关收集器 API 函数的更多信息,请参见动态函数和模块

对于 Java 程序,大部分方法由 JVM 软件解释。在解释执行期间,在单独的线程上运行的 Java HotSpot 虚拟机会监视性能。在监视过程中,虚拟机可以决定使用已解释的一个或多个方法,生成相应的机器码,并执行更有效的机器码版本,而不是解释原始代码。

对于 Java 程序,无需使用收集器 API 函数;分析器通过在该方法的索引行下方使用一个特殊行,表明在带注释的反汇编代码列表中存在 Java HotSpot 编译的代码,如以下示例所示。


                     11.    public int add_int () {
                     12.       int       x = 0;
                         <Function: Routine.add_int()>
2.832     2.832          Routine.add_int() <HotSpot-compiled leaf instructions>
0.        0.             [ 12] 00000000: iconst_0
0.        0.             [ 12] 00000001: istore_1

反汇编代码列表仅显示已解释的字节代码,而不显示编译的指令。缺省情况下,在该特殊行的旁边显示已编译代码的度量。该独占和包含 CPU 时间与各行已解释字节代码的所有独占和包含 CPU 时间总和不同。通常,如果多次调用该方法,已编译指令的 CPU 时间将大于已解释字节代码的 CPU 时间总和,原因是已解释代码只是在最初调用该方法时执行一次,而已编译代码则在其后执行。

带注释的源代码不显示 Java HotSpot 编译的函数,而是显示一个特殊索引行,以指示不带行号的指令。例如,下面显示了与上例显示的反汇编提取对应的带注释的源代码:


                     11.    public int add_int () {
2.832     2.832        <Function: Routine.add_int(), instructions without line numbers>
0.        0.         12.       int       x = 0;
                       <Function: Routine.add_int()>

Java 本机函数

本机代码是最初用 C、C++ 或 Fortran 编写,由 Java 代码通过 Java 本地接口 (Java Native Interface, JNI) 调用的已编译代码。以下示例来自与演示程序 jsynprog 关联的文件 jsynprog.java 的带注释的反汇编代码。


                     5. class jsynprog
                        <Function: jsynprog.<init>()>
0.       5.504          jsynprog.JavaCC() <Java native method>
0.       1.431          jsynprog.JavaCJava(int) <Java native method>
0.       5.684          jsynprog.JavaJavaC(int) <Java native method>
0.       0.             [  5] 00000000: aload_0
0.       0.             [  5] 00000001: invokespecial <init>()
0.       0.             [  5] 00000004: return

由于本机方法不包含在 Java 源代码中,jsynprog.java 的带注释的源代码的开头会显示每个 Java 本机方法,并使用一个特殊的索引行来指示不带行号的指令。


0.       5.504          <Function: jsynprog.JavaCC(), instructions without line 
                           numbers>
0.       1.431          <Function: jsynprog.JavaCJava(int), instructions without line 
                           numbers>
0.       5.684          <Function: jsynprog.JavaJavaC(int), instructions without line 
                           numbers>

注 –

在实际的带注释的源代码显示中,索引行并不换行。


克隆函数

编译器能够识别可以对其执行额外优化的函数调用。所传递的部分参数是常量的函数调用即是这类调用的其中一个示例。当编译器识别出它可以优化的特定调用时,会创建该函数的副本(称为克隆),并生成优化代码。

在带注释的源代码中,编译器注释将指示是否创建了克隆函数:


0.       0.       Function foo from source file clone.c cloned, 
                   creating cloned function _$c1A.foo; 
                   constant parameters propagated to clone
0.       0.570     27.    foo(100, 50, a, a+50, b);

注 –

在实际的带注释的源代码显示中,编译器注释行并不换行。


克隆函数名称是标识特定调用的重整名称 (mangled name)。在上面的示例中,编译器注释指示克隆的函数名称为 _$c1A.foo。在函数列表中,该函数显示为:


0.350     0.550     foo
0.340     0.570     _$c1A.foo

因为每个克隆函数都具有不同的指令集,所以带注释的反汇编代码列表会分别显示这些克隆函数。这些函数与任何源文件都没有关联,因此其指令也与任何源代码行号无关。以下显示了某个克隆函数的带注释的反汇编代码的前几行。


0.       0.           <Function: _$c1A.foo>
0.       0.           [?]    10e98:  save        %sp, -120, %sp
0.       0.           [?]    10e9c:  sethi       %hi(0x10c00), %i4
0.       0.           [?]    10ea0:  mov         100, %i3
0.       0.           [?]    10ea4:  st          %i3, [%i0]
0.       0.           [?]    10ea8:  ldd         [%i4 + 640], %f8

静态函数

静态函数通常在库中使用,因此库内部使用的名称不会与用户可能使用的名称发生冲突。库被剥离后,静态函数的名称将从符号表中删除。在这种情况下,分析器会为包含剥离静态函数的库中的每个文本区域生成人工名称。该名称的格式为 <static>@0x12345,其中 @ 符号后的字符串是库中文本区域的偏移量。分析器无法区分连续的剥离静态函数和单个这样的函数,因此可能出现两个或多个这样的函数,且其度量合并在一起。可在 jsynprog 演示程序的函数列表中找到静态函数的示例,如下所示。


0.       0.       <static>@0x18780
0.       0.       <static>@0x20cc
0.       0.       <static>@0xc9f0
0.       0.       <static>@0xd1d8
0.       0.       <static>@0xe204

在 "PC" 标签中,上述函数使用如下所示的偏移量表示:


0.       0.       <static>@0x18780 + 0x00000818
0.       0.       <static>@0x20cc + 0x0000032C
0.       0.       <static>@0xc9f0 + 0x00000060
0.       0.       <static>@0xd1d8 + 0x00000040
0.       0.       <static>@0xe204 + 0x00000170

在 "PC" 标签中,在被剥离的库内调用的函数的替代表示方法为:


<library.so> -- no functions found + 0x0000F870

包含度量

在带注释的反汇编代码中,有一些特殊的行标记外联函数占用的时间。

以下示例显示了当调用外联函数时显示的带注释的反汇编代码:


0.       0.        43.         else
0.       0.        44.         {
0.       0.        45.                 printf("else reached\n");
0.       2.522         <inclusive metrics for outlined functions>

分支目标

带注释的反汇编代码列表中显示的一行人工行 <branch target> 与指令的某个 PC 对应,在该指令中使用回溯查找它的有效地址失败,原因是该回溯算法遇到了分支目标。

在不运行实验的情况下查看源代码/反汇编代码

无需运行实验,使用 er_src 实用程序就可以查看带注释的源代码和带注释的反汇编代码。输出的生成方式与在分析器中的生成方式相同,只是不显示任何度量。er_src 命令的语法如下所示:


er_src [ -func | -{source,src} item tag | -disasm item tag |
-{cc,scc,dcc} com_spec | -outfile filename | -V ] object

object 是可执行文件、共享对象或目标文件(.o 文件)的名称。

item 是用于生成可执行文件或共享对象的函数、源文件或目标文件的名称。item 还可以采用 functionfile’ 的格式指定,在这种情况下,er_src 将在指定文件的源上下文中显示指定函数的源代码或反汇编代码。

tag 是索引,用于决定当多个函数具有相同的名称时引用哪个 item。该选项是必需选项,但如果没有必要解析函数,则可以忽略该选项。

特殊的项和标记 all -1 指示 er_src 为该对象中的所有函数生成带注释的源代码或反汇编代码。


注 –

在可执行文件和共享对象上使用 all -1 生成的输出可能非常大。


以下几节将介绍 er_src 实用程序接受的选项。

-func

列出给定对象的所有函数。

-{source,src} item tag

显示列出的项的带注释的源代码。

-disasm item tag

在列表中包括反汇编代码。缺省列表不包括反汇编代码。如果没有可用的源代码,则产生一个不带编译器注释的反汇编代码列表。

-{c,scc,dcc} com-spec

指定要显示哪些编译器注释类。com-spec 是用冒号隔开的类列表。如果使用的是 -scc 选项,则对源代码编译器注释应用 com-spec 类列表;如果使用的是 -dcc 选项,则对反汇编代码注释应用类列表;如果使用的是 -c 选项,则既对源代码注释,也对反汇编代码注释应用类列表。有关这些类的描述,请参见控制源代码和反汇编代码列表的命令

注释类可以在缺省文件中指定。首先读取系统范围的 er.rc 缺省文件,然后读取用户起始目录中的 .er.rc 文件(如果存在),最后读取当前目录中的 .er.rc 文件。起始目录中的 .er.rc 文件的缺省值覆盖系统的缺省值,而当前目录中 .er.rc 文件的缺省值覆盖起始目录和系统的缺省值。这些文件也由分析器和 er_print 实用程序使用,但只有源代码和反汇编代码编译器注释的设置由 er_src 实用程序使用。有关这些缺省文件的描述,请参见设置缺省值的命令。缺省文件中除 sccdcc 之外的命令均被 er_src 实用程序忽略。

-outfile filename

打开显示列表输出的文件 filename。缺省情况下,如果文件名为短划线 (-),则输出会写入 stdout

-V

打印当前发行版本