22

所以,

细节

让我们假设我们有一些问题,并且至少有两个解决方案。而我们想要实现的 - 是比较他们的有效性。这个怎么做?显然,最好的答案是:做测试。而且我怀疑对于特定于语言的问题(例如“什么对 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,我们可以说代码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- 即它将使用多少内存。“多少”是指“函数执行期间的最大内存使用量”。很明显,我们不能像测量执行时间时那样行事——因为我们对它之外的功能一无所知——它是一个黑匣子。如果事实上,我们甚至无法确定该函数是否会成功执行(例如,想象一下如果以某种方式将内部分配为 1E6 会发生$x什么$ybaz。因此,将我们的代码包装在函数中可能不是一个好主意。但是如果代码本身包含其他函数/方法调用呢?

我的方法

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

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

此外,垃圾收集已启用。我正在使用 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()取消注册刻度处理程序。这就是麻烦所在:这个函数需要传递字符串。所以你不能取消注册滴答处理程序,它是闭包(因为它会尝试对其进行字符串化,这将导致致命错误,因为PHP__toString()中的 Closure类中没有方法)。为什么会这样?没有别的,就是一个bug. 我希望修复将很快完成。

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

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

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

更新:由于功能要求,我为此测量工具添加了作曲家包。

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

这应该已经说明了我要说的所有内容。

使用刻度处理程序并在每次执行时将内存使用情况打印到文件中,文件行包含:

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_usage() 更适合的 memory_get_peak_usage( )

于 2013-11-14T08:56:12.083 回答
0

刚刚偶然发现

http://3v4l.org/

尽管他们没有提供有关如何分别执行基准测试的详细信息,但不要认为很多人在他们办公桌下的 VM 上并行运行超过 100 个 PHP 版本;)

于 2014-08-15T01:37:23.750 回答
0

这可能不是您正在寻找的,但您可以使用XDebug获取该信息

于 2013-11-14T09:02:25.957 回答