Утечка памяти?! Правильно ли работает сборщик мусора при использовании '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
не так ли?