编译器注释指示如何生成编译器优化代码。为了将编译器注释行同索引行及初始源代码行区分开,以蓝色显示编译器注释行。编译器的各个部分可将注释合并到可执行文件中。每个注释都与源代码的特定行相关联。当写入带注释的源代码后,任何源代码行的编译器注释会直接显示在源代码行的前面。
编译器注释描述为了优化源代码而对其进行的大量转换。这些转换包括循环优化、并行化、内联和流水线作业。以下显示了一个编译器注释的示例。
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 软件执行概述。