Sun Studio 12:性能分析器

如何将度量分配到程序结构

使用与特定事件的数据一起记录的调用栈将度量分配到程序指令。如果该信息可用,则会将每条指令都映射到一行源代码,而分配到该指令的度量也被分配到该行源代码。有关此过程的更多详细说明,请参见第 7 章,了解性能分析器及其数据

除了源代码和指令,还会将度量分配到更高级别的对象:函数和装入对象。调用栈包含在执行分析时记录的有关函数调用到达指令地址的序列的信息。性能分析器使用调用栈来计算程序中每个函数的度量。这些度量称为函数级度量。

函数级度量:独占、包含和归属

性能分析器可计算三种类型的函数级度量:独占度量、包含度量和归属度量。

对于只出现在调用栈底部的函数(叶函数),独占度量和包含度量是相同的。

对于装入对象,也要计算独占度量和包含度量。装入对象的独占度量通过累加装入对象中所有函数上函数级别的度量计算得出。装入对象的包含度量与函数的包含度量的计算方法相同。

函数的独占度量和包含度量给出了有关所有通过函数记录的路径信息。归属度量给出了有关通过函数记录的特定路径的信息。这些度量显示了度量在多大程度上来自特定函数调用。调用中所涉及的两个函数分别为调用者被调用者对于调用树中的每个函数:

各度量间的关系可通过以下等式表示:

显示各度量间关系的等式

通过比较调用者或被调用者的归属度量和包含度量,可以得到以下进一步的信息:

要定位可改善程序性能的位置,请执行以下操作:

解释归属度量:示例

图 2–1 中说明了独占、包含和归属度量,该图包含完整的调用树。其中的焦点是中心函数,即函数 C。

图的后面显示了该程序的伪代码。

图 2–1 说明独占、包含和归属度量的调用树

说明独占、包含和归属度量的调用树。

Main 函数调用了函数 A 和函数 B,并将其 10 个单位的包含度量归属到函数 A,将 20 个单位的包含度量归属到函数 B。这些是函数 Main 的被调用者归属度量。它们的总和 (10+20) 加上函数 Main 的独占度量等于函数 main 的包含度量 (32)。

由于函数 A 将其所有时间都花费在对函数 C 的调用上,因此它的独占度量为 0 个单位。

函数 C 由以下两个函数调用:函数 A 和函数 B,并将其 10 个单位的包含度量归属到函数 A,将 15 个单位的包含度量归属到函数 B。这些是调用者归属度量。它们的总和 (10+15) 等于函数 C 的包含度量 (25)。

调用者归属度量等于函数 A 及 B 的包含度量和独占度量之间的差额,这意味着这两个函数只调用函数 C。(实际上,这两个函数可能会调用其他函数,但时间太短,在实验中不显示。)

函数 C 调用了两个函数,函数 E 和函数 F,并分别将其 10 个单位的包含度量归属到函数 E 和函数 F。这些是被调用者归属度量。它们的总和 (10+10) 加上函数 C 的独占度量 (5) 等于函数 C 的包含度量 (25)。

函数 E 及函数 F 的被调用者归属度量和被调用者包含度量是相同的。这意味着函数 E 及函数 F 仅由 C 调用。函数 E 的独占度量和包含度量是相同的,但函数 F 的这两种度量不同。这是因为函数 F 调用了其他函数(函数 G),但函数 E 没有调用。

下面显示了该程序的伪代码。

    main() {
       A();
       /Do 2 units of work;/
       B();
    }

    A() {
       C(10);
    }

    B() {
       C(7.5);
       /Do 5 units of work;/
       C(7.5);
    }

    C(arg) {
          /Do a total of "arg" units of work, with 20% done in C itself,
          40% done by calling E, and 40% done by calling F./
    }

递归如何影响函数级度量

递归函数直接或间接的调用使得度量的计算复杂化。性能分析器将函数的度量作为一个整体显示,而不是显示函数的每个调用的度量:因此,必须将一系列递归调用的度量压缩为单一度量。这不会影响通过调用栈底部的函数(叶函数)计算得出的独占度量,但会影响包含度量和归属度量。

包含度量是通过将事件的度量添加到调用栈中函数的包含度量来计算的。为了确保在递归调用栈中不重复计算度量,事件的度量仅会添加到每个唯一函数的包含度量。

归属度量是通过包含度量计算得出的。在最简单的递归中,递归函数具有两个调用者:它本身和另一个函数(初始化函数)。如果在最后的调用中完成了所有工作,则会将递归函数的包含度量归属到它本身,而不是初始化函数。之所以发生此归属,是因为递归函数的所有更高调用的包含度量均被视为零,以避免重复计算度量。但是,初始化函数会由于递归调用而做为被调用者正确归属到递归函数的包含度量部分。