4

我在 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
    )
)

坏的

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,我发现这种糟糕的方式太频繁了。

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

4

3 回答 3

10

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

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

内部的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);
}

包含对函数的引用(= 名称)的字符串在此处被明确且可访问。现在,每次create_function()调用时,旧函数都会被新函数覆盖。

所以,不,不存在“内存泄漏”,它就是以这种方式工作的。

当然下面的代码效率更高:

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

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

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

于 2014-09-15T09:30:30.773 回答
3

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

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

于 2014-09-15T09:19:57.653 回答
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,不是吗?

于 2014-09-15T09:07:13.317 回答