可在性能分析器中查看实验的带注释的源代码,方法是选择分析器窗口左窗格中的“源”标签。另外,可以使用 er_src 实用程序,在不运行实验的情况下查看带注释的源代码。本手册的此部分介绍了源代码如何在性能分析器中显示。有关使用 er_src 实用程序查看带注释的源代码的详细信息,请参见在不运行实验的情况下查看源代码/反汇编代码。
初始源文件的内容
每行可执行源代码的性能度量
由于度量超过特定阈值而突出显示的代码行
索引行
编译器注释
“源”标签分为若干列,其中左侧固定宽度的几列显示各个度量,右侧的其余部分显示带注释的源代码。
在带注释的源代码中,所有以黑色显示的行都来自于初始源文件。在带注释的源代码列中,每行开头的数字与初始源文件中的行号对应。以不同颜色显示其中字符的行可能是索引行,也可能是编译器注释行。
源文件是指可编译生成目标文件或解释为字节代码的任何文件。目标文件通常包含与源代码中的函数、子例程或方法对应的一个或多个可执行代码区域。分析器分析目标文件,标识每个作为函数的可执行区域,并尝试将其在目标代码中找到的函数映射至与目标代码相关联的源文件中的函数、例程、子例程或方法。当分析器操作成功后,它将在带注释的源文件中对应于在目标代码中找到的函数的第一条指令处添加一个索引行。
带注释的源代码会为每个函数(包括内联函数,即使内联函数没有在“函数”标签中的列表中显示)显示一个索引行。在“源”标签中以红色斜体显示索引行,且索引行中的文本括在尖括号中。类型最简单的索引行与函数的缺省上下文对应。任何函数的缺省源上下文都被定义为该函数的第一条指令所归属的源文件。以下示例显示了 C 函数 icputime 的索引行。
578. int 579. icputime(int k) 0. 0. 580. { <Function: icputime> |
从上面的示例能够看出,索引行紧跟在第一条指令行的后面。对于 C 源代码,第一条指令对应于函数体开头的开型花括号处。在 Fortran 源代码中,各个子例程的索引行跟在包含 subroutine 关键字的行后面。同样,main 函数索引行跟在应用程序启动时执行的第一个 Fortran 源代码指令的后面,如以下示例所示:
1. ! Copyright (c) 2006, 2010, Oracle and/or its affiliates. All Rights Reserved. 2. ! @(#)omptest.f 1.11 10/03/24 SMI 3. ! Synthetic f90 program, used for testing openmp directives and the 4. ! analyzer 5. 0. 0. 0. 0. 6. program omptest <Function: MAIN> 7. 8. !$PRAGMA C (gethrtime, gethrvtime) |
有时,分析器可能无法将它在对象代码中找到的函数映射至与该对象代码相关联的源文件中的任何编程指令;例如,可能从另一个文件(如某个头文件)执行代码 #included 或内联操作。
此外,以红色显示的行还有一些特殊索引行,以及其他一些不是编译器注释的特殊行。例如,由于编译器优化的原因,可能会为目标代码中的某个函数创建一个特殊索引行,但该索引行并不对应在任何源文件中编写的代码。有关详细信息,请参阅“源”、“反汇编”和 "PC" 标签中的特殊行。
编译器注释指示如何生成编译器优化代码。为了将编译器注释行同索引行及初始源代码行区分开,以蓝色显示编译器注释行。编译器的各个部分可将注释合并到可执行文件中。每个注释都与源代码的特定行相关联。当写入带注释的源代码后,任何源代码行的编译器注释会直接显示在源代码行的前面。
编译器注释描述为了优化源代码而对其进行的大量转换。这些转换包括循环优化、并行化、内联和流水线作业。以下显示了一个编译器注释的示例。
0. 0. 0. 0. 28. SUBROUTINE dgemv_g2 (transa, m, n, alpha, b, ldb, & 29. & c, incc, beta, a, inca) 30. CHARACTER (KIND=1) :: transa 31. INTEGER (KIND=4) :: m, n, incc, inca, ldb 32. REAL (KIND=8) :: alpha, beta 33. REAL (KIND=8) :: a(1:m), b(1:ldb,1:n), c(1:n) 34. INTEGER :: i, j 35. REAL (KIND=8) :: tmr, wtime, tmrend 36. COMMON/timer/ tmr 37. Function wtime_ not inlined because the compiler has not seen the body of the routine 0. 0. 0. 0. 38. tmrend = tmr + wtime() Function wtime_ not inlined because the compiler has not seen the body of the routine Discovered loop below has tag L16 0. 0. 0. 0. 39. DO WHILE(wtime() < tmrend) Array statement below generated loop L4 0. 0. 0. 0. 40. a(1:m) = 0.0 41. Source loop below has tag L6 0. 0. 0. 0. 42. DO j = 1, n ! <=-----\ swapped loop indices Source loop below has tag L5 L5 cloned for unrolling-epilog. Clone is L19 All 8 copies of L19 are fused together as part of unroll and jam L19 scheduled with steady-state cycle count = 9 L19 unrolled 4 times L19 has 9 loads, 1 stores, 8 prefetches, 8 FPadds, 8 FPmuls, and 0 FPdivs per iteration L19 has 0 int-loads, 0 int-stores, 11 alu-ops, 0 muls, 0 int-divs and 0 shifts per iteration L5 scheduled with steady-state cycle count = 2 L5 unrolled 4 times L5 has 2 loads, 1 stores, 1 prefetches, 1 FPadds, 1 FPmuls, and 0 FPdivs per iteration L5 has 0 int-loads, 0 int-stores, 4 alu-ops, 0 muls, 0 int-divs and 0 shifts per iteration 0.210 0.210 0.210 0. 43. DO i = 1, m 4.003 4.003 4.003 0.050 44. a(i) = a(i) + b(i,j) * c(j) 0.240 0.240 0.240 0. 45. END DO 0. 0. 0. 0. 46. END DO 47. END DO 48. 0. 0. 0. 0. 49. RETURN 0. 0. 0. 0. 50. END |
可以使用“设置数据显示”对话框中的“源/反汇编”标签来设置在“源”标签中显示的编译器注释类型;有关详细信息,请参见设置数据表示选项。
一种很常见的优化是可以识别在多个位置出现的同一个表达式,并可通过在一个位置生成该表达式的代码来提高性能。例如,如果在一个代码块的 if 和 else 分支同时出现了相同的操作,则编译器可以仅将该操作移到 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) 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 至源代码行的映射,此外,它还必须能够读取源文件来生成要显示的带注释的副本。分析器依次在以下缺省位置搜索源文件、目标文件和可执行文件,并在找到具有正确基本名称的文件时停止:
实验的归档目录
当前工作目录
可执行文件或编译对象中记录的绝对路径名
缺省值可以通过 addpath 或 setpath 指令或者在分析器中使用“设置数据显示”对话框进行更改。
如果使用由 addpath 或 setpath 设置的路径列表找不到文件,则可以使用 pathmap 命令指定一个或多个路径重映射。使用 pathmap 命令,可以指定 old-prefix 和 new-prefix。在以 old-prefix 所指定的前缀开头的源文件、目标文件或共享对象的任何路径名中,旧的前缀将由 new-prefix 所指定的前缀替代。然后,使用所得到的路径查找文件。可采用多个 pathmap 命令,并逐一尝试,直到找到文件为止。
编译过程要经过很多阶段,这取决于请求的优化级别,并且会发生转换,该转换会使指令到源代码行的映射变得混乱。对于某些优化,源代码行信息可能完全丢失,而对于其他优化,源代码行信息则可能变得混乱。编译器依赖各种试探操作来跟踪指令的源代码行,而这些试探操作不是绝对无误的。
指令的度量必须解释为在等待执行指令时累积的度量。如果记录事件时执行的指令来自与叶 PC 相同的源代码行,则度量可以解释为由于执行了该源代码行的缘故。但是,如果叶 PC 与所执行的指令来自不同的源代码行,那么叶 PC 所属源代码行的度量中至少有一些度量必须解释为在等待执行此代码行时累积的度量。例如,当一个源代码行上计算的值被用在下一个源代码行上时。
当执行过程中存在严重的延迟(如高速缓存未命中或资源队列停止)或指令等待前一个指令返回结果时,如何解释度量的问题是最重要的问题。在这些情况下,源代码行的度量看上去很高(高得不太合理),应该查看代码中的其他邻近的行以找出导致高度量值的行。
表 7–1中说明了可能出现在带注释的源代码行上的四种度量格式。
表 7–1 带注释的源代码度量