Sun Studio 12:性能分析器

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

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

标识初始源代码行

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

“源”标签中的索引行

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

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