内存泄漏?!垃圾收集器在'array_map'中使用'create_function'时是否正确?

alg*_*thm 4 php garbage-collection memory-leaks create-function array-map

我在StackOverflow上找到了以下解决方案,以从对象数组中获取特定对象属性的数组:PHP - 从对象数组中提取属性

建议的解决方案是使用array_mapand within创建一个函数,create_function如下所示:

$catIds = array_map(create_function('$o', 'return $o->id;'), $objects);
Run Code Online (Sandbox Code Playgroud)

会发生什么?:array_map在这种情况下,每个数组元素都会运行一个stdClass对象.首先,它创建一个这样的函数:

function($o) {
    return $o->id;
}
Run Code Online (Sandbox Code Playgroud)

其次,它在当前迭代中为对象调用此函数.它的工作原理与它类似的解决方案几乎相同:

$catIds = array_map(function($o) { return $o->id; }, $objects);
Run Code Online (Sandbox Code Playgroud)

但是这个解决方案只能在PHP版本> = 5.3中运行,因为它使用的Closure概念=> http://php.net/manual/de/class.closure.php

现在真正的问题:

第一个解决方案create_function增加了内存,因为创建的函数将被写入内存而不会被重用或销毁.在它的第二个解决方案中Closure.

因此,解决方案给出了相同的结果,但在内存方面有不同的行为.

以下示例:

// following array is given
$objects = array (
    [0] => stdClass (
        [id] => 1
    ),
    [1] => stdClass (
        [id] => 2
    ),
    [2] => stdClass (
        [id] => 3
    )
)
Run Code Online (Sandbox Code Playgroud)

while (true)
{
    $objects = array_map(create_function('$o', 'return $o->id;'), $objects);
    // result: array(1, 2, 3);

    echo memory_get_usage() ."\n";

    sleep(1);
}

4235616
4236600
4237560
4238520
...
Run Code Online (Sandbox Code Playgroud)

while (true)
{
    $objects = array_map(function($o) { return $o->id; }, $objects);
    // result: array(1, 2, 3);

    echo memory_get_usage() ."\n";

    sleep(1);
}

4235136
4235168
4235168
4235168
...
Run Code Online (Sandbox Code Playgroud)

我花了这么多时间来找到它,现在我想知道,如果它是垃圾收集器的错误或我犯了错误?为什么将已经创建和调用的函数留在内存中是有意义的,何时它永远不会被重用?

以下是一个运行示例:http://ideone.com/9a1D5g

更新:当我递归搜索我的代码及其依赖项,例如PEAR和Zend时,我经常发现这种BAD方式.

更新:当嵌套两个函数时,我们从内到外继续以评估此表达式.换句话说,它首先启动create_function(一次)并且返回的函数名称是单个调用的参数array_map.但是因为GC忘记将它从内存中删除(没有指针留在内存中的函数)并且PHP无法重用已经位于内存中的函数让我觉得有错误而且不仅仅是"性能不好"的东西.这个特定的代码行是PHPDoc中的一个示例,并在许多大型框架中重用,例如Zend和PEAR等.有了一行,你可以解决这个"错误",检查.但我不是在寻求解决方案:我在寻找真相.这是一个错误还是仅仅是我的方法.后者我还不能决定.

KIK*_*are 10

在使用的情况下create_function()创建lambda样式的函数eval(),并返回包含其名称的字符串.然后将该名称作为参数传递给array_map()函数.

这与闭包式匿名函数不同,后者根本不使用包含名称的字符串.function($o) { return $o->id; } 函数,或者更确切地说是Closure类的一个实例.

eval()功能,内部create_function(),执行一块PHP代码创建想要的功能.有点像这样:

function create_function($arguments,$code) 
{
  $name = <_lambda_>; // just a unique string
  eval('function '.$name.'($arguments){$code}');
  return $name;
}
Run Code Online (Sandbox Code Playgroud)

请注意,这是一种简化.

因此,一旦创建了该函数,它将一直持续到脚本结束,就像脚本中的普通函数一样.在上面的BAD示例中,在循环的每次迭代中都会像这样创建一个新函数,占用越来越多的内存.

但是,你可以故意破坏lambda风格的功能.这很简单,只需将循环更改为:

while (true)
{
    $func = create_function('$o', 'return $o->id;');
    $objects = array_map($func, $objects);
    echo memory_get_usage() ."\n";
    sleep(1);
}
Run Code Online (Sandbox Code Playgroud)

包含函数引用(= name)的字符串是expliciet,可在此处访问.现在,每次create_function()调用时,旧函数都会被新函数覆盖.

所以,不,没有"内存泄漏",它意味着以这种方式工作.

当然,下面的代码更有效:

$func = create_function('$o', 'return $o->id;');

while (true)
{
    $objects = array_map($func, $objects);
    echo memory_get_usage() ."\n";
    sleep(1);
}
Run Code Online (Sandbox Code Playgroud)

并且只应在PHP版本不支持闭包式匿名函数时使用.