PHP中的基准内存使用情况

时间:2013-11-14 08:41:11

标签: php performance memory benchmarking

SO,

具体细节信息

让我们假设我们有一些问题,至少有两个解决方案。我们想要实现的目标是比较它们的有效性。这该怎么做?显然,最好的答案是:做测试。我怀疑在语言特定问题上有更好的方法(例如“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
   ];
}

- 这符合我们的问题,可用于执行比较基准测试。在比较下,我的意思是我们可以使用上面的函数代码X,然后代码Y,之后我们可以说代码X是{ {1}}比代码Z%更快/更慢。

问题

好的,我们可以轻松测量时间。但记忆呢?我们之前的假设“如果我们想测试一些代码,它等于测试一些函数”在这里似乎不正确。为什么?因为 - 从形式上看它是真的,但如果我们将代码隐藏在函数中,那么我们将永远无法在此之后测量内存。例如:

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被分配为1E6,将会发生什么。因此,将我们的代码包装在函数中可能不是一个好主意。但是,如果代码本身包含其他函数/方法调用呢?

我的方法

我目前的想法是以某种方式创建一个函数,它将在每个输入代码的行之后测量内存。这意味着这样的事情:让我们有代码

baz

- 做完一些事后,测量功能会做:

$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()函数,然后重复我的分析。等等。
  • 第二 - 正如我所提到的,有一些语言特定的东西只能通过测试来解决。这就是为什么有时间测量的自动方式是我的目标。

此外,启用了垃圾收集。我使用PHP 5.5(我相信这很重要)

问题

我们如何有效地测量某些功能的内存使用情况?它可以在PHP中实现吗?可能有一些简单的代码(如foo()函数用于上面的时间测量)?

5 个答案:

答案 0 :(得分: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中,可以从函数内部declaring滴答,我们可以使用register_tick_function()的回调。所以我的意思是 - 使用匿名函数,它将使用我的基准函数的本地上下文。我已成功创造了这一点。但是,我不想影响全局上下文,因此我希望使用unregister_tick_function()取消注册ticks处理程序。这就是问题所在:这个函数期望传递字符串。所以你不能取消注册tick处理程序,这是一个闭包(因为它会尝试对它进行字符串化,这将导致致命的错误,因为PHP中的Closure class中没有__toString()方法。为什么会这样?这不是别的,而是一个bug。我希望尽快修复。

还有什么其他选择?我想到的最简单的选择是使用global变量。但它们很奇怪,而且我想避免它的副作用。我不想影响上下文。但是,实际上,我们可以在一些类中包装我们需要的所有内容,然后通过call_user_func_array()调用tick函数。而且call_user_func_array只是字符串,所以我们可以克服这种错误的PHP行为,并成功完成整个过程。

更新:我已经实现了measurement tool。我在那里添加了时间测量和自定义回调定义的测量。随意使用它。

更新:此答案中提到的错误现已修复,因此无需使用注册为tick函数的call_user_func()进行操作。现在可以直接创建和使用闭包。

更新:由于功能请求,我为此测量工具添加了composer package

答案 1 :(得分: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']);

然后查看文件以查看内存如何随时间变化。

您也可以稍后通过单独的程序解析该文件以分析峰值等。

(通过调用内部函数来查看可能的峰值,你需要将结果存储到一个变量中,否则它将在tick处理程序测量内存之前被释放)

答案 2 :(得分:2)

您可以使用提供内存使用信息的XDebuga patch for XDebug

如果无法做到这一点,你可以随时使用我认为比memory_get_usage()更适合的memory_get_peak_usage()

答案 3 :(得分:0)

这可能不是您想要的,但您可以使用XDebugget at that information

答案 4 :(得分:0)

偶然发现了

http://3v4l.org/

虽然他们没有提供有关如何实施基准测试的详细信息 - 但是并不认为很多人在桌面下的VM上并行运行了100多个PHP版本;)