Alm*_* Do 22 php memory performance benchmarking
所以,
细节
让我们假设我们有一些问题,至少有两个解决方案.我们想要实现的目标是比较它们的有效性.这该怎么做?显然,最好的答案是:做测试.我怀疑在语言特定的问题上有更好的方法(例如"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
];
}
Run Code Online (Sandbox Code Playgroud)
- 这符合我们的问题,可以用来做比较基准测试.在比较我的意思是,我们可以使用以上功能的代码X,然后代码Y并在此之后,我们可以说,代码X是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
}
Run Code Online (Sandbox Code Playgroud)
- 我们想测试baz- 即它将使用多少内存."多少"我的意思是"执行功能时最大内存使用量".很明显,我们不能像测量执行时间那样行事 - 因为我们对它外面的功能一无所知 - 它是一个黑盒子.事实上,我们甚至不能确定函数是否会被成功执行(想象一下,如果以某种方式$x和$y内部baz将被分配为1E6 将会发生什么).因此,将我们的代码包装在函数中可能不是一个好主意.但是,如果代码本身包含其他函数/方法调用呢?
我的方法
我目前的想法是以某种方式创建一个函数,它将在每个输入代码行之后测量内存.这意味着:让我们有代码
$x = foo();
echo($x);
$y = bar();
Run Code Online (Sandbox Code Playgroud)
- 做完一些事后,测量功能会做:
$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
Run Code Online (Sandbox Code Playgroud)
- 但这看起来很奇怪,也没有回答问题 - 如何衡量功能内存使用情况.
用例
用例:在大多数情况下,复杂性理论至少可以big-O为某些代码提供估计.但:
foo()函数占用了太多内存.我将要做的?是的,转到此foo()功能,并重复我的分析.等等.此外,启用垃圾收集.我使用PHP 5.5(我相信这很重要)
这个问题
我们如何有效地测量某些功能的内存使用情况?它可以在PHP中实现吗?可能有一些简单的代码(如benchmark上面的时间测量函数)?
Alm*_* Do 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]));
Run Code Online (Sandbox Code Playgroud)
- 它完全符合我的要求:
现在,一些背景.在PHP中,可以从函数内部声明 ticks,我们可以使用register_tick_function()的回调.所以我的意思是 - 使用匿名函数,它将使用我的基准函数的本地上下文.我已成功创造了这一点.但是,我不想影响全局上下文,因此我希望使用unregister_tick_function()取消注册ticks处理程序.这就是问题所在:这个函数需要传递字符串.所以你不能取消注册tick处理程序,这是闭包(因为它会尝试对其进行字符串化,这将导致致命错误,因为PHP __toString()中的Closure 类中没有方法).为什么会这样?这不是别的,而是一个错误.我希望尽快修复.
还有什么其他选择?我想到的最简单的选择是使用全局变量.但它们很奇怪,也是我想避免的副作用.我不想影响上下文.但是,实际上,我们可以在一些类中包装我们需要的所有内容,然后通过call_user_func_array()调用tick函数.并且call_user_func_array只是字符串,所以我们可以克服这种错误的PHP行为并成功完成整个过程.
更新:我已经实现了测量工具.我在那里添加了时间测量和自定义回调定义的测量.随意使用它.
更新:这个答案中提到的Bug现在已经修复,因此不需要使用call_user_func(),注册为tick函数.现在可以直接创建和使用闭包.
更新:由于功能请求,我为此测量工具添加了composer包.
declare(ticks=1); // should be placed before any further file loading happens
Run Code Online (Sandbox Code Playgroud)
这应该已经说明了我要说的一切.
使用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']);
Run Code Online (Sandbox Code Playgroud)
然后查看文件以查看内存如何随时间变化.
您也可以稍后通过单独的程序解析该文件以分析峰值等.
(要通过调用内部函数来查看可能的峰值,您需要将结果存储到变量中,否则它将在tick处理程序测量内存之前被释放)