synprog のパフォーマンスデータを収集し、test.1.er を開いたことを確認してください。 これらの作業がまだ済んでいない場合には、synprog サンプルのデータ収集を参照して実行してください。
ここでは、関数で使用される時間と関数の呼び出し元との関係について、「呼び出し元-呼び出し先」タブを使用して検証します。
「呼び出し元-呼び出し先」タブは、3 つの区画に分割されています。 選択した関数は、中央区画に表示されています。 上の区画には選択した関数の呼び出し元、下の区画には選択した関数によって呼び出される関数 (呼び出し先) が表示されます。
「呼び出し元」区画には、選択した関数を呼び出す 2 つの関数、gpf_b() と gpf_a() が表示されます。 gpf_work() は他の関数を呼び出さないので、「呼び出し先」区画は空です。 こういった関数のことを、「リーフ関数」と呼びます。
「呼び出し元」区画のユーザー CPU 時間を見てみましょう。 呼び出し元が使用した時間は、呼び出し元からの呼び出しによって関数で費やされた時間です。 gpf_work() の大部分の時間は、gpf_b() からの呼び出しに基づいています。 gpf_a() からの呼び出しでは、あまり時間が費やされていません。
gpf_b() からの呼び出しが gpf_a() からの呼び出しに比べ、gpf_work() で 10 倍の時間がかかっている理由を知るには、この 2 つの呼び出し元のソースコードを調査する必要があります。
gpf_a() が現在選択されている関数となり、中央区画に移動します。その呼び出し元が「呼び出し元」区画に表示され、その呼び出し先である gpf_work() は「呼び出し先」区画に表示されます。
gpf_a() は引数に 1 を設定して gpf_work() を 10 回呼び出します。一方、gpf_b() は gpf_work() を 1 回しか呼び出しませんが、引数に 10 を設定しています。 gpf_a() と gpf_b() の引数は、gpf_work() の仮引数 amt に渡されます。
次に、gpf_work() のコードを調べ、gpf_work() の呼び出し方法によってなぜ違いが発生するのかを調べましょう。
変数 imax が演算される行を調べます。 imax は、for ループの上限です。gpf_work() に要する時間は、引数 amt の 2 乗に依存します。 したがって、引数に 10 を指定した関数からの 1 回の呼び出し (繰り返し回数が 400 回) には、引数に 1 を指定した関数からの 10 回の呼び出し (4 回の繰り返しが 10 回) に比べ、約 10 倍の時間がかかります。
ところが gprof では、関数の引数やその他のアクセス可能なデータが時間に及ぼす影響を考慮することなく、関数の呼び出し回数に基づいて関数に要する時間を見積もります。 したがって synprog の解析の場合、gprof であれば、gpf_a() からの呼び出しに gpf_b() からの呼び出しの 10 倍の時間を当てることになります。 これが、gprof の誤った推論です。
関連項目 | |
---|---|
「呼び出し元-呼び出し先」タブ 「ソース」タブ 排他的メトリック、包括的メトリック、属性メトリック |