Утечка памяти?! Правильно ли работает сборщик мусора при использовании 'create_function' внутри 'array_map'?

Я нашел следующее решение здесь, в Stackru, чтобы получить массив определенного свойства объекта из массива объектов: PHP - извлечение свойства из массива объектов

Предлагаемое решение заключается в использовании 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 concept => 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 и других. С помощью еще одной строки вы можете обойти эту "ошибку", проверьте. Но я не ищу решение: я ищу правду. Это ошибка или это только мой подход. И последнее я еще не мог решить.

3 ответа

Решение

В случае create_function() функция лямбда-стиля создается с использованием 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;
}

Обратите внимание, что это упрощение.

Таким образом, после создания функции она будет сохраняться до конца сценария, как обычные функции в сценарии. В приведенном выше примере с BAD новая функция создается таким образом на каждой итерации цикла, занимая все больше и больше памяти.

Однако вы можете преднамеренно уничтожить функцию лямбда-стиля. Это довольно просто, просто измените цикл на:

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.

Не использовать create_function() если вы можете избежать этого. Особенно не многократно. В соответствии с большой желтой рамкой в ​​руководстве по PHP:

... имеет плохую производительность и характеристики использования памяти.

Хорошо, я думаю, что проблема в том, что первое решение с 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не так ли?

Другие вопросы по тегам