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

时间:2014-09-12 12:29:40

标签: php memory-leaks garbage-collection array-map create-function

我在StackOverflow上找到了以下解决方案,以从对象数组中获取特定对象属性的数组:PHP - Extracting a property from an array of objects

建议的解决方案是使用array_map并在create_function内创建一个函数,如下所示:

$catIds = array_map(create_function('$o', 'return $o->id;'), $objects);

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

function($o) {
    return $o->id;
}

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

$catIds = array_map(function($o) { return $o->id; }, $objects);

但是此解决方案仅在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
    )
)

BAD

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
...

不可

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
...

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

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

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

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

3 个答案:

答案 0 :(得分:11)

create_function()的情况下,使用eval()创建lambda样式的函数,并返回包含其名称的字符串。然后将该名称作为参数传递给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;
}

请注意,这是一种简化。

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

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

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

包含函数引用(= 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);
}

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

答案 1 :(得分:3)

如果可以避免,请不要使用create_function()。特别不重复。根据{{​​3}}中的大黄色警告框:

  

...它具有糟糕的性能和内存使用特性。

答案 2 :(得分:2)

好吧,我认为问题是,create_function的第一个解决方案是在旧版本的PHP上运行,而第二个解决方案并没有增加不需要的内存。但是,让我们来看看第一个解决方案。 create_function方法在array_map内调用,即每次while次迭代。如果我们想要一个解决方案来使用旧的PHP版本而不增加内存,我们必须在每个while次迭代中对旧的函数实例进行跟踪:

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

这就是全部。这么简单。

但它根本没有回答这个问题。剩下的问题是它是否是PHP或功能的错误。根据我的理解,在变量中写create_function的结果的方式应该与将它直接作为参数放在array_map中相同,不是吗?