13.7 Profiler Example

Last-modified: 2025-03-16 (日) 13:12:04

13.7 プロファイラの例

以下に、プロファイラ セッションの簡単な例を示します。プロファイラ関数の詳細なドキュメントについては、プロファイリングを参照してください。次のコードを考えてみましょう。

global N A;
N = 300;
A = rand (N, N);
function xt = timesteps (steps, x0, expM)
 global N;
 if (steps == 0)
   xt = NA (N, 0);
 else
   xt = NA (N, steps);
   x1 = expM * x0;
   xt(:, 1) = x1;
   xt(:, 2 : end) = timesteps (steps - 1, x1, expM);
 endif
endfunction
function foo ()
 global N A;
 initial = @(x) sin (x);
 x0 = (initial (linspace (0, 2 * pi, N)))';
 expA = expm (A);
 xt = timesteps (100, x0, expA);
endfunction
function fib = bar (N)
 if (N <= 2)
   fib = 1;
 else
   fib = bar (N - 1) + bar (N - 2);
 endif
endfunction

2 つの主な関数を実行すると、次のようになります。

tic; foo; toc;
⇒ Elapsed time is 2.37338 seconds.
tic; bar (20); toc;
⇒ Elapsed time is 2.04952 seconds.

しかし、これだけでは、この時間がどこで費やされているかについてはあまり情報が得られません。たとえば、 への単一の呼び出しの方expmがコストが高いのか、それとも再帰的なタイムステップ自体の方がコストが高いのかなどです。より詳細な情報を得るには、プロファイラーを使用できます。

profile on;
foo;
profile off;
data = profile ("info");
profshow (data, 10);
This prints a table like:
  #  Function Attr     Time (s)        Calls

  7      expm             1.034            1
  3  binary *             0.823          117
 41  binary \             0.188            1
 38  binary ^             0.126            2
 43 timesteps    R        0.111          101
 44        NA             0.029          101
 39  binary +             0.024            8
 34      norm             0.011            1
 40  binary -             0.004          101
 33   balance             0.003            1

エントリは実行された個々の関数(最も重要な10個のみ)と、それぞれの情報です。「バイナリ *' は演算子を表し、他のエントリは通常の関数です。これらには、組み込みの のような関数 expmと独自のルーチン (たとえば) の両方が含まれます。このプロファイルから、 は 1 回しか呼び出されないにもかかわらず、処理時間の大部分をtimesteps消費することがすぐにわかりますexpm。2 番目に時間のかかる演算は、ルーチン の行列ベクトル積です

timesteps. 6

ただし、タイミングはプロファイルから得られる唯一の情報ではありません。属性列には、がtimesteps自分自身を再帰的に呼び出すことが示されています。これはこの例ではそれほど目立たないかもしれませんが (いずれにしても明らかなので)、より複雑な設定では役立つ可能性があります。なぜ「バイナリ \' という出力があれば、これも簡単に解明できます。 は、フィールド を含むdata構造体配列 (構造体配列FunctionTable) であることに注意してください。ここには、表示されているプロファイルの生データが格納されます。表の最初の列の番号は、表示されている関数が見つかるインデックスを示します。検索すると、data.FunctionTable(41)次のようになります。

 scalar structure containing the fields:
   FunctionName = binary \
   TotalTime =  0.18765
   NumCalls =  1
   IsRecursive = 0
   Parents =  7
   Children = [](1x0)

ここで、表の情報を再度確認しますが、フィールド と が追加されていますParents。Childrenこれらは両方とも配列であり、問​​題の関数を直接呼び出した関数 (expmこの場合はエントリ 7、 ) または問題の関数によって呼び出された関数 (関数なし) のインデックスが含まれています。したがって、バックスラッシュ演算子は によって内部的に使用されていますexpm。

それでは、 を見てみましょうbar。このために、新しいプロファイリング セッションを開始します (profile onは次の処理を行います。プロファイラーが再起動される前に古いデータが削除されます)。

profile on;
bar (20);
profile off;
profshow (profile ("info"));

これにより、次のようになります。

  #            Function Attr     Time (s)        Calls

  1                 bar    R        2.091        13529
  2           binary <=             0.062        13529
  3            binary -             0.042        13528
  4            binary +             0.023         6764
  5             profile             0.000            1
  8               false             0.000            1
  6              nargin             0.000            1
  7           binary !=             0.000            1
  9 __profiler_enable__             0.000            1

当然のことながら、barも再帰的です。 フィボナッチ数を次善の方法で再帰的に計算する過程で 13,529 回呼び出され、ほとんどの時間がbarそれ自体に費やされました。

foo 最後に、 との両方の実行を一緒にプロファイリングするとしますbar。 の実行時データがすでに収集されているためbar、既存のデータを消去せずにプロファイラーを再起動し、 に関する不足している統計情報を収集できますfoo。これは次のように行います。

profile resume;
foo;
profile off;
profshow (profile ("info"), 10);

下の表からわかるように、現在、両方のプロファイルが混在しています。

  #  Function Attr     Time (s)        Calls

  1       bar    R        2.091        13529
 16      expm             1.122            1
 12  binary *             0.798          117
 46  binary \             0.185            1
 45  binary ^             0.124            2
 48 timesteps    R        0.115          101
  2 binary <=             0.062        13529
  3  binary -             0.045        13629
  4  binary +             0.041         6772
 49        NA             0.036          101

脚注
(6)
バイナリ乗算演算子であることだけが分かっていますが、幸いにもこの演算子はコード内の 1 か所にしか出現しないため、どの箇所に多くの時間がかかっているかが分かります。複数の箇所に出現する場合は、階層プロファイルを使用して、時間のかかる箇所を正確に特定する必要がありますが、この例ではその点については触れていません。