22

それで、

仕様

何らかの問題があり、それに対する少なくとも 2 つの解決策があるとします。そして、私たちが達成したいのは、それらの有効性を比較することです. これを行う方法?明らかに、最良の答えはdo testsです。そして、言語固有の質問に関しては、より良い方法があるとは思えません (たとえば、「PHP のほうが速いのはどれですか? echo 'foo', 'bar'」などecho('foo'.'bar'))。

さて、あるコードをテストしたい場合、それはある機能をテストすることと同じであると仮定します。なんで?そのコードを関数にラップし、そのコンテキスト (存在する場合) をパラメーターとして渡すことができるためです。したがって、必要なのは、たとえば、すべてのことを行うベンチマーク関数を持つことだけです。これは非常に単純なものです:

function benchmark(callable $function, $args=null, $count=1)
{
   $time = microtime(1);
   for($i=0; $i<$count; $i++)
   {
      $result = is_array($args)?
                call_user_func_array($function, $args):
                call_user_func_array($function);
   }
   return [
      'total_time'   => microtime(1) - $time,
      'average_time' => (microtime(1) - $time)/$count,
      'count'        => $count
   ];
}

-これは私たちの問題に適合し、比較ベンチマークを行うために使用できます。比較の下では、上記の function を code に使用しX、次に code に使用でき、その後、 codeはcode よりも高速/低速でYあると言えます。XZ%Y

問題

よし、簡単に時間を計測できるぞ。しかし、メモリはどうですか?以前の仮定「あるコードをテストしたい場合、それはある機能をテストすることと同じである」という仮定は、ここでは正しくないようです。なんで?なぜなら、形式的には正しいのですが、コードを関数内に隠すと、その後メモリを測定することができなくなります。例:

function foo($x, $y)
{
   $bar = array_fill(0, $y, str_repeat('bar', $x));
   //do stuff
}

function baz($n)
{
   //do stuff, resulting in $x, $y
   $bee = foo($x, $y);
   //do other stuff
}

-そして、テストしたいbaz-つまり、どれだけのメモリを使用するか。「どのくらい」とは、 「関数の実行中の最大メモリ使用量」を意味します。そして、実行時間を測定していたときのように振る舞えないことは明らかです - それ以外の機能については何も知らないので、それはブラックボックスです. 実際には、関数が正常に実行されるかどうかさえ確信できません (たとえば、何らかの方法$x$y内部bazが 1E6 として割り当てられた場合に何が起こるか想像してみてください)。したがって、コードを関数内にラップするのは得策ではないかもしれません。しかし、コード自体に他の関数/メソッド呼び出しが含まれている場合はどうなるでしょうか?

私のアプローチ

私の現在のアイデアは、各入力コードの行の後にメモリを測定する関数を何らかの方法で作成することです。これは次のようなことを意味します: コードを用意しましょう

$x = foo();
echo($x);
$y = bar();

-そして、何かをした後、測定機能が行います:

$memory = memory_get_usage();
$max    = 0;

$x = foo();//line 1 of code
$memory = memory_get_usage()-$memory;
$max    = $memory>$max:$memory:$max;
$memory = memory_get_usage();

echo($x);//second line of code
$memory = memory_get_usage()-$memory;
$max    = $memory>$max:$memory:$max;
$memory = memory_get_usage();

$y = bar();//third line of code
$memory = memory_get_usage()-$memory;
$max    = $memory>$max:$memory:$max;
$memory = memory_get_usage();

//our result is $max

-しかし、それは奇妙に見え、質問にも答えません-関数のメモリ使用量を測定する方法。

使用事例

このユースケース: ほとんどの場合、複雑性理論は特定のコードに対して少なくともbig-O推定を提供できます。しかし:

  • まず、コードは巨大になる可能性があります。手作業による分析はできるだけ避けたいと考えています。そして、それが私の現在のアイデアが悪い理由です。適用することはできますが、コードを手動で操作する必要があります。さらに、コードの構造をさらに深く掘り下げるには、再帰的に適用する必要があります。たとえば、トップレベルに適用した後、一部のfoo()関数がメモリを消費しすぎることがわかりました。私は何をしますか?はい、このfoo()関数に移動して、.. その中で私の分析を繰り返します。等々。
  • 第 2 に、前述したように、テストを実行することによってのみ解決できる言語固有の問題がいくつかあります。そういうわけで、時間測定のような自動化された方法を持つことが私の目標です。

また、ガベージ コレクションが有効になっています。私はPHP 5.5を使用しています(これは重要だと思います)

質問

特定の関数のメモリ使用量を効果的に測定するにはどうすればよいでしょうか? PHPで実現できますか?簡単なコード(benchmark上記の時間測定機能など)で可能でしょうか?

4

5 に答える 5

11

@bwoebiが目盛りを使用して素晴らしいアイデアを提案した後、私はいくつかの調査を行いました。今、私はこのクラスで私の答えを持っています:

class Benchmark
{
   private static $max, $memory;

   public static function memoryTick()
   {
      self::$memory = memory_get_usage() - self::$memory;
      self::$max    = self::$memory>self::$max?self::$memory:self::$max;
      self::$memory = memory_get_usage();
   }

   public static function benchmarkMemory(callable $function, $args=null)
   {
      declare(ticks=1);
      self::$memory = memory_get_usage();
      self::$max    = 0;

      register_tick_function('call_user_func_array', ['Benchmark', 'memoryTick'], []);
      $result = is_array($args)?
                call_user_func_array($function, $args):
                call_user_func($function);
      unregister_tick_function('call_user_func_array');
      return [
         'memory' => self::$max
      ];
   }
}

//var_dump(Benchmark::benchmarkMemory('str_repeat', ['test',1E4]));
//var_dump(Benchmark::benchmarkMemory('str_repeat', ['test',1E3]));

-だから、私が望むことを正確に行います:

  • ブラックボックスです
  • 渡された関数の最大使用メモリを測定します
  • 文脈から独立している

さて、いくつかの背景。PHP では、ティックの宣言は関数内から可能であり、register_tick_function()のコールバックを使用できます。だから私のことは - 私のベンチマーク関数のローカルコンテキストを使用する匿名関数を使用することでした。そして、私はそれをうまく作成しました。ただし、グローバル コンテキストに影響を与えたくないので、unregister_tick_function()でティック ハンドラーの登録を解除します。ここで問題が発生します。この関数は、文字列が渡されることを想定しています。そのため、クロージャーである tick ハンドラーの登録を解除することはできません ( PHP__toString()の Closureクラスにメソッドがないため、致命的なエラーを引き起こす文字列化を試みるため)。なぜそうなのですか?バグ以外の何物でもない. 修正がすぐに行われることを願っています。

他のオプションは何ですか?私が考えていた最も簡単なオプションは、グローバル変数を使用することでした。しかし、それらは奇妙であり、避けたい副作用でもあります。コンテキストに影響を与えたくありません。しかし、実際には、必要なものをすべてクラスにラップしてから、call_user_func_array()を介して tick 関数を呼び出すことができます。Andcall_user_func_arrayは単なる文字列であるため、このバグのある PHP の動作を克服し、すべてを正常に実行できます。

更新:これから測定ツールを実装しました。そこに時間測定とカスタム コールバック定義の測定を追加しました。お気軽にご利用ください。

更新call_user_func():この回答で言及されているバグは修正されたため、 tick 関数として登録された をトリックする必要はありません。クロージャを直接作成して使用できるようになりました。

更新: 機能のリクエストにより、この測定ツールのcomposer パッケージを追加しました。

于 2013-11-14T11:49:38.767 に答える
9
declare(ticks=1); // should be placed before any further file loading happens

それは私が言うことすべてをすでに言っているはずです。

tick ハンドラーを使用し、実行ごとにメモリ使用量を次のファイル行でファイルに出力します。

function tick_handler() {
    $mem = memory_get_usage();
    $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[0];
    fwrite($file, $bt["file"].":".$bt["line"]."\t".$mem."\n");
}
register_tick_function('tick_handler'); // or in class: ([$this, 'tick_handler']);

次に、ファイルを見て、行ごとにメモリが時間とともにどのように変化するかを確認します。

後で別のプログラムでそのファイルを解析して、ピークなどを分析することもできます。

(そして、内部関数を呼び出して可能なピークがどのようになるかを確認するには、結果を変数に保存する必要があります。そうしないと、ティック ハンドラーがメモリを測定する前に既に解放されます)

于 2013-11-14T09:33:50.467 に答える
2

XDebugと、メモリ使用量の情報を提供するXDebugのパッチを使用できます。

これが不可能な場合は、いつでもmemory_get_peak_usage()を使用できます。これは、memory_get_usage() よりも適していると思います

于 2013-11-14T08:56:12.083 に答える
0

これはまさにあなたが探しているものではないかもしれませんが、おそらくXDebugを使用してその情報を取得できます。

于 2013-11-14T09:02:25.957 に答える