捕获(外部)给定回调的内存消耗


Capturing (externally) the memory consumption of a given Callback

问题

假设我有这样一个函数:

function hog($i = 1) // uses $i * 0.5 MiB, returns $i * 0.25 MiB
{
    $s = str_repeat('a', $i * 1024 * 512); return substr($s, $i * 1024 * 256);
}

我想调用它,并能够检查它使用的最大内存量。

也就是 memory_get_function_peak_usage($callback); 。这可能吗?


What I Have try

我使用以下值作为hog()的非单调递增的$i参数:

$iterations = array_merge(range(0, 50, 10), range(50, 0, 5));
$iterations = array_fill_keys($iterations, 0);

本质上是:

(
    [0] => 0
    [10] => 0
    [20] => 0
    [30] => 0
    [40] => 0
    [50] => 0
    [45] => 0
    [35] => 0
    [25] => 0
    [15] => 0
    [5] => 0
)

memory_get_usage()

封闭
foreach ($iterations as $key => $value)
{
    $alpha = memory_get_usage(); hog($key);
    $iterations[$key] = memory_get_usage() - $alpha;
}
print_r($iterations);
输出:

(
    [0] => 96
    [10] => 0
    [20] => 0
    [30] => 0
    [40] => 0
    [50] => 0
    [45] => 0
    [35] => 0
    [25] => 0
    [15] => 0
    [5] => 0
)

如果我存储hog()的返回值,结果开始看起来更真实:

foreach ($iterations as $key => $value)
{
    $alpha = memory_get_usage(); $s = hog($key);
    $iterations[$key] = memory_get_usage() - $alpha; unset($s);
}
print_r($iterations);
输出:

(
    [0] => 176
    [10] => 2621536
    [20] => 5242976
    [30] => 7864416
    [40] => 10485856
    [50] => 13107296
    [45] => 11796576
    [35] => 9175136
    [25] => 6553696
    [15] => 3932256
    [5] => 1310816
)

和预期的一样,现在它显示了返回的内存量,但是我需要使用的总内存量


使用register_tick_function():

我不知道,但事实证明,当你知道的时候:

declare (ticks=1)
{
    $a = hog(1);
}

它不会为hog()函数内的每一行,语句或代码块打勾,只会为declare块内的代码打勾——所以,除非在其中定义了函数,否则这个选项是不允许的。


gc_*函数混合使用:

我尝试(没有太多的希望我必须说)使用gc_disable(), gc_enable()gc_collect_cycles()的组合与上面的两个实验,看看是否有什么改变-它没有。

我在PHP手册中挖掘,我发现memtrack扩展,不完美,但它的东西。


EDIT:我听说过它,但从未真正尝试过。原来XHProf是我所需要的:

$flags = array
(
    XHPROF_FLAGS_CPU,
    XHPROF_FLAGS_MEMORY,
    XHPROF_FLAGS_NO_BUILTINS,
);
$options = array
(
    'ignored_functions' => array
    (
        'call_user_func',
        'call_user_func_array',
        'xhprof_disable',
    ),
);
function hog($i = 1) // uses $i * 0.5 MiB, returns $i * 0.25 MiB
{
    $s = str_repeat('a', $i * 1024 * 512); return substr($s, $i * 1024 * 256);
}

测试# 1:

xhprof_enable(array_sum($flags), $options);
hog(4);
$profile = xhprof_disable();
print_r($profile);
输出:

    [main()==>hog] => Array
        (
            [ct] => 1
            [wt] => 54784
            [mu] => 384
            [pmu] => 3142356
        )
    [main()] => Array
        (
            [ct] => 1
            [wt] => 55075
            [mu] => 832
            [pmu] => 3142356
        )

mu为内存占用率,pmu为内存占用率峰值,3142356 / 1024 / 1024 / 0.5 = 4 = $i .


Test #2(不含XHPROF_FLAGS_NO_BUILTINS):

    [hog==>str_repeat] => Array
        (
            [ct] => 1
            [wt] => 21890
            [cpu] => 4000
            [mu] => 2097612
            [pmu] => 2094200
        )
    [hog==>substr] => Array
        (
            [ct] => 1
            [wt] => 17202
            [cpu] => 4000
            [mu] => 1048992
            [pmu] => 1048932
        )
    [main()==>hog] => Array
        (
            [ct] => 1
            [wt] => 45978
            [cpu] => 8000
            [mu] => 1588
            [pmu] => 3143448
        )
    [main()] => Array
        (
            [ct] => 1
            [wt] => 46284
            [cpu] => 8000
            [mu] => 2132
            [pmu] => 3143448
        )

Whoohoo !由于Facebook !


从XHProf文档:

值得澄清的是,XHProf并不严格跟踪每一个分配/自由操作。相反,它使用了一个更简单的方案。它跟踪分配给PHP的内存量的增加/减少在每个函数的入口和出口之间。它还跟踪增加/减少分配给PHP的峰值内存的数量

这与变量作用域有关。函数结束后,函数内的所有内容都将被清除。因此,如果您需要知道总共使用了多少内存,则需要在函数作用域之外声明它们。我的投票将去一个单一的变量数组,以方便,如果你需要多个变量。如果你只需要一个,那么显然不需要数组。

<?php
$outervar = array();
function hog($i = 1) // uses $i * 0.5 MiB, returns $i * 0.25 MiB 
{ 
  global $outervar;
  $outervar['s'] = str_repeat('a', $i * 1024 * 512); return substr($outervar['s'], $i * 1024 * 256); 
}
foreach ($iterations as $key => $value) 
{ 
  $alpha = memory_get_usage(); 
  hog($key); 
  $iterations[$key] = memory_get_usage() - $alpha;
  $outervar = array(); 
 }
print_r($iterations);

由于我们不存储hog的结果,因此将使用0.5mb*$i。如果您还需要返回值,即使它没有存储,首先将其保存到$outervar['result']或其他东西,然后返回它。但是,如果您保存它,它将被双倍计数。

第二个选项是通过引用&$memusage给出第二个形参,并在函数中使用memory_get_usage()部分,并将结果存储在byref变量
中。

我发现:github.com/kampaw/profiler似乎使用了"tick/register/declare -concept",这不是你的选择。我还读到,register_tick_function将在PHP 6中删除。(但这可能只是谣言)

我完全理解你在函数内部检查内存的意思。

我已经测试了你的代码基于只是调用函数,然后返回内存使用之后使用该函数。我做了一个checkMemoryFunction(),只是为了使它更通用(当然checkMemoryFunction会占用一点内存,但如果有必要,可以减去)。我想你想的是对的,但是我发现了另一件很奇怪的事情……

…使用下面的代码:

<?php
function hog($i) 
{
    $s = str_repeat('a', $i * 1024 * 512); 
    return substr($s, $i * 1024 * 256);
}
function checkMemoryFunction($functionName, $i) {
    $startMemory = memory_get_usage();
    call_user_func($functionName, $i);
    return memory_get_usage() - $startMemory;
}
$iterations = array_merge(range(0, 50, 10), range(50, 0, 5));
$iterations = array_fill_keys($iterations, 0);
foreach ($iterations as $key => $value)
{
    $mem = checkMemoryFunction('hog', $key);
    $iterations[$key] = $mem;
}
$peak = max($iterations);
echo '<hr />Iteratins array:';
print_r($iterations);
echo 'memory peak=' . $peak;
?>

我得到了和你一样的结果:(第一个元素集,但不是其余的)

输出迭代数组:

Array ( [0] => 312 [10] => 0 [20] => 0 [30] => 0 [40] => 0 [50] => 0 [45] => 0 [35] => 0 [25] => 0 [15] => 0 [5] => 0 ) memory peak=312

然而,当我添加行设置每个键值为0(或任何值)…

$iterations = array_merge(range(0, 50, 10), range(50, 0, 5));
$iterations = array_fill_keys($iterations, 0);
// set each value to 0 in array
foreach ($iterations as $key => &$value)
{
    $value = 0;
}
foreach ($iterations as $key => $value)
{
    $mem = checkMemoryFunction('hog', $key);
    $iterations[$key] = $mem;
}

…我得到这些值(所有函数调用的一些内存使用):

Array ( [0] => 312 [10] => 24 [20] => 24 [30] => 24 [40] => 24 [50] => 24 [45] => 24 [35] => 24 [25] => 24 [15] => 24 [5] => 24 ) memory peak=312

所以似乎问题在于对array_fill_keys()的调用。(似乎数组元素没有以某种奇怪的方式初始化)

仔细查看合并数组后代码中的$iterations-array,它看起来是这样的:(重复值)

Array ( [0] => 0 [1] => 10 [2] => 20 [3] => 30 [4] => 40 [5] => 50 [6] => 50 [7] => 45 [8] => 40 [9] => 35 [10] => 30 [11] => 25 [12] => 20 [13] => 15 [14] => 10 [15] => 5 [16] => 0` )

但我认为你真正想要的是这样的东西:

Array ( [0] => 0 [1] => 10 [2] => 20 [3] => 30 [4] => 40 [5] => 50 [6] => 45 [8] => 35 [10] => 25 [12] => 15 [14] => 5) 

我的怀疑是重复使array_fill_keys()以奇怪的方式行事,所以我尝试:

$iterations = array(0, 10, 20, 30, 40, 50, 45, 35, 25, 15);
$iterations = array_fill_keys($iterations, 0);
foreach ($iterations as $key => $value)
{
        $mem = checkMemoryFunction('hog', $key);
        $iterations[$key] = $mem;
}

但它仍然没有像预期的那样工作:

Array ( [0] => 312 [10] => 0 [20] => 0 [30] => 0 [40] => 0 [50] => 0 [45] => 0 [35] => 0 [25] => 0 [15] => 0 ) memory peak=312

添加

foreach ($iterations as $key => &$value)
{
    $value = 0;
}

还是像预期的那样:

Array ( [0] => 312 [10] => 0 [20] => 24 [30] => 24 [40] => 24 [50] => 24 [45] => 32 [35] => 48 [25] => 24 [15] => 24 [5] => 24 ) memory peak=312

我认为这很奇怪,因为array_fill_keys($iterations, 0);应该和上面的foreach做同样的事情。我不明白为什么它不像预期的那样工作。也许这是一个bug,但也可能是一些我没有想到的"愚蠢"的东西。

另一种方法是将php源文件中的"内容存储在函数中",然后将其保存为profile/hog.php,然后执行hog.php代码。

我希望这能帮助你一点!: -)