PHP中的基准内存使用

PHP中的基准内存使用,php,performance,memory,benchmarking,Php,Performance,Memory,Benchmarking,所以 细节 让我们假设我们有一些问题和至少两种解决方案。我们想要实现的是比较它们的有效性。如何做到这一点?显然,最好的答案是:做测试。我怀疑有没有更好的方法来解决特定于语言的问题(例如“什么对PHP来说更快:echo'foo',bar'或echo'foo.'bar')) 好的,现在我们假设如果我们想测试一些代码,就等于测试一些函数。为什么?因为我们可以将代码包装到函数中,并将其上下文(如果有)作为参数传递。因此,我们所需要的就是,例如,有一些基准函数,可以完成所有的工作。这里有一个非常简单的例子

所以

细节

让我们假设我们有一些问题和至少两种解决方案。我们想要实现的是比较它们的有效性。如何做到这一点?显然,最好的答案是:做测试。我怀疑有没有更好的方法来解决特定于语言的问题(例如“什么对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
   ];
}
-我们想测试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()
    函数,然后。。重复我的分析。等等
  • 第二,正如我所提到的,有些特定于语言的问题只能通过测试来解决。这就是为什么我的目标是拥有一些自动的方式,比如时间测量
此外,还启用了垃圾收集。我正在使用PHP5.5(我相信这很重要)

问题

我们怎样才能有效地测量某些函数的内存使用情况?在PHP中可以实现吗?是否可以使用一些简单的代码(如上面的
基准测试
时间测量功能)

您可以使用和,它提供内存使用信息


如果这是不可能的,您可以始终使用我认为比内存使用更合适的工具。

这可能不是您想要的,但您可能会使用它

这应该已经说明了我将要说的一切

使用勾号处理程序,在每次执行时将内存使用情况打印到一个文件,文件行带有:

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处理程序测量内存之前,它已经被释放)

在提出了使用ticks的好主意之后,我做了一些研究。现在我对这门课有了答案:

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中,滴答声可以从函数内部进行,我们可以使用回调函数来实现。所以我的想法是使用匿名函数,它将使用我的基准函数的本地上下文。我已经成功地创造了它。但是,我不想影响全局上下文,所以我希望使用注销标记处理程序。这就是问题所在:这个函数希望传递字符串。因此,您不能取消注册tick处理程序,这就是closure(因为它将尝试将其字符串化,这将导致致命错误,因为PHP中closure中没有
\uu toString()
方法)。为什么会这样?这不是别的,只是一个简单的问题。我希望很快就能修好

还有什么其他选择?我想到的最简单的选择是使用变量。但它们很奇怪,而且这也是我想要避免的副作用。我不想影响上下文。但是,实际上,我们可以将所有需要的东西封装在某个类中,然后通过调用tick函数。而
call\u user\u func\u array
仅仅是字符串,因此我们可以克服这种错误的PHP行为,并成功地完成整个工作

更新:我已经实现了这个。我在这里添加了时间度量和自定义回调定义的度量。请随意使用它

更新:这个答案中提到的Bug现在已经修复,所以不需要使用注册为tick函数的
call\u user\u func()
。现在可以直接创建和使用闭包

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

刚刚偶然发现


虽然他们没有提供基准测试的详细信息,但是他们分别实施了一些措施——不要认为很多人在办公桌下的虚拟机上并行运行了100多个PHP版本;)

正如您所说,这在很大程度上取决于内存管理,如果垃圾收集
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']);
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]));