メトリックスは、イベント固有のデータとともに記録される呼び出しスタックを使用して、プログラムの命令に対応付けられます。情報を利用できる場合には、あらゆる命令がそれぞれ 1 つのソースコード行にマップされ、その命令に割り当てられたメトリックスも同じソースコード行に対応付けられます。この仕組みについての詳細は、第 6 章パフォーマンスアナライザとそのデータについてを参照してください。
メトリックスは、ソースコードと命令のほかに、より上位のオブジェクト (関数とロードオブジェクト) にも対応付けられます。呼び出しスタックには、プロファイルが取られたときに記録された命令アドレスに達するまでに行われた、一連の関数呼び出しに関する情報が含まれます。パフォーマンスアナライザは、この呼び出しスタックを使用し、プログラム内の各関数のメトリックスを計算します。こうして得られたメトリックスを関数レベルのメトリックスといいます。
パフォーマンスアナライザが計算する関数レベルのメトリックスには、排他的メトリックス、包括的メトリックス、および属性メトリックスの 3 種類があります。
関数の排他的メトリックスは、関数自体の内部で発生するイベントにより計算されます。これには、ほかの関数の呼び出しから発生したメトリックスは含まれません。
包括的メトリックスは、関数の内部で発生するイベントと、その関数から呼び出す関数により計算されます。これには、ほかの関数の呼び出しにから発生したメトリックが含まれます。
属性メトリックスは、どれだけの包括的メトリックが、別の関数からの呼び出しによるものか、または別の関数の呼び出しによるものかを示します。つまり、属性メトリックスは別の関数に起因するメトリックです。
呼び出しスタックの一番下にのみ現れる関数 (リーフ関数) では、その関数の排他的および包括的メトリックスは同じになります。
排他的および包括的メトリックスは、ロードオブジェクトについても計算されます。ロードオブジェクトの排他的メトリックスは、そのロードオブジェクト内の全関数の関数レベルのメトリックスを集計することによって計算されるメトリックスです。ロードオブジェクトの包括的メトリックスは、関数に対するのと同じ方法で計算されるメトリックスです。
関数の排他的および包括的メトリックスは、その関数を通るすべての記録経路に関する情報を提供します。属性メトリックスは、関数を通る特定の経路に関する情報を提供します。その情報は、どれだけのメトリックが特定の関数呼び出しが原因で発生したかを示します。その呼び出しに関係する 2 つの関数は、呼び出し元および呼び出し先として表されます。呼び出しツリー内のそれぞれの関数では次のことが言えます。
関数の呼び出し元の属性メトリックスは、その関数の包括的メトリックのうち、各呼び出し元からの呼び出しが原因になっているメトリックスを示します。呼び出し元の属性メトリックスを合計したものが、関数の包括的メトリックです。
関数の呼び出し先の属性メトリックスは、その関数の包括的メトリックのうち、各呼び出し先への呼び出しが原因になっているメトリックスを示します。それらの合計に、その関数の排他的メトリックを加えたものが、その関数の包括的メトリックに等しくなります。
メトリックス間の関係は、次の等式で表すことができます。
呼び出し元または呼び出し先の属性メトリックスと包括的メトリックスを比較すると、さらに情報が得られます。
呼び出し元の属性メトリックとその包括的メトリックの差は、ほかの関数への呼び出しおよびその呼び出し元自体の動作が原因で発生したメトリックを示します。
呼び出し先の属性メトリックとその包括的メトリックの差は、その呼び出し先の包括的メトリックのうち、ほかの関数からのその呼び出し先への呼び出しが原因で発生したメトリックを示します。
プログラムのパフォーマンス改善が可能な場所を見つける方法には、次のものがあります。
包括的メトリックスを使用して、プログラム内のどの呼び出しシーケンスが大きなメトリック値の原因になっているかを判定します。
属性メトリックスを使用して、大きなメトリック値の原因になっている特定の 1 つまたは複数の関数に対する呼び出しシーケンスをトレースします。
図 2–1 は、排他的メトリックス、包括的メトリックス、属性メトリックスを完全な呼び出しツリーで表しています。ここでは、中央の関数の関数 C に 注目します。
プログラムの擬似コードは、図のあとに示されています。
Main 関数は、関数 A および関数 B を呼び出し、Main 関数の包括的メトリックのうちの関数 A の 10 単位と関数 B の 20 単位の原因となっています。これらは Main 関数の呼び出し先の属性メトリックです。その合計 (10+20) に Main 関数の排他的メトリックを加算すると、Main 関数の包括的メトリック (32) に等しくなります。
関数 A は関数 C の呼び出しにすべての時間を使用するため、排他的メトリックスは 0 単位です。
関数 C は、関数 A および 関数 B の 2 つの関数によって呼び出され、関数 C の包括的メトリックのうちの関数 A の 10 単位と関数 B の 15 単位の原因になっています。これらは呼び出し元の属性メトリックスです。その合計 (10+15) は、関数 C の包括的メトリック (25) に等しくなります。
呼び出し元の属性メトリックは、関数 A および関数 B の包括的メトリックスと排他的メトリックスの差に等しくなり、それぞれが関数 C のみを呼び出すことを意味します (実際のところ、各関数はほかの関数を呼び出すことがありますが、その時間はかなり短いため、実験には現れません)。
関数 C は、関数 E および関数 F の 2 つの関数を呼び出し、関数 C の包括的メトリックのうちの関数 E の 10 単位と関数 F の 10 単位の原因となっています。これらは呼び出し先の属性メトリックスです。その合計 (10+10) に関数 C の排他的メトリック (5) を加算すると、関数 C の包括的メトリック (25) に等しくなります。
呼び出し先の属性メトリックと呼び出し先の包括的メトリックは、関数 E でも関数 F でも同じです。これは、関数 E と関数 F のどちらも、関数 C によってのみ呼び出されることを意味します。排他的メトリックおよび包括的メトリックは、関数 E では同じですが、関数 F では異なります。この理由は、関数 F は別の関数である関数 G を呼び出しますが、関数 E は関数 G を呼び出さないためです。
このプログラムの擬似コードを次に示します。
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./ }
直接または間接のどちらの場合も、再帰関数呼び出しがあると、メトリックスの計算が複雑になります。パフォーマンスアナライザは、関数の呼び出しごとのメトリックスではなく、関数全体としてのメトリックスを表示します。このため、一連の再帰的な呼び出しのメトリックスを 1 つのメトリックに要約する必要があります。この要約によって、呼び出しスタックの最後の関数 (リーフ関数) から計算される排他的メトリックスが影響を受けることはありませんが、包括的および属性メトリックスは影響を受けます。
包括的メトリックスは、イベントのメトリックと呼び出しスタック内の関数の包括的メトリックを合計することによって計算されます。再帰呼び出しスタックでメトリックが複数回カウントされないようにするには、イベントのメトリックが、同じ関数の包括的メトリックに 1 回だけ加算されるようにします。
属性メトリックスは、包括的メトリックスから計算されます。もっとも簡単な再帰では、再帰関数は、それ自身ともう 1 つの関数 (呼び出しを開始する関数) の 2 つの呼び出し元を持ちます。最後の呼び出しですべての動作を終えた場合、再帰関数の包括的メトリックの原因になるのは、その再帰関数であり、呼び出しを開始した関数ではありません。この現象が起こるのは、メトリックが複数回カウントされるのを避けるために、再帰関数の上位の呼び出しすべてについて、包括的メトリックがゼロと見なされるためです。ただし、呼び出しを開始した関数は、再帰呼び出しであるために、呼び出し先としての再帰関数の包括的メトリックの一部の原因になります。