Почему этот простой php-скрипт утечки памяти?

В надежде избежать будущих утечек памяти в php-программах (модули drupal и т. Д.) Я возился с простыми сценариями php, которые утечки памяти.

Может ли эксперт php помочь мне найти, что об этом сценарии приводит к тому, что использование памяти постоянно растет?

Попробуйте запустить его самостоятельно, изменяя различные параметры. Результаты интересны. Вот:

<?php function memstat() { print "current memory usage: ". memory_get_usage() . "\n"; } function waste_lots_of_memory($iters) { $i = 0; $object = new StdClass; for (;$i < $iters; $i++) { $object->{"member_" . $i} = array("blah blah blha" => 12345); $object->{"membersonly_" . $i} = new StdClass; $object->{"onlymember"} = array("blah blah blha" => 12345); } unset($object); } function waste_a_little_less_memory($iters) { $i = 0; $object = new StdClass; for (;$i < $iters; $i++) { $object->{"member_" . $i} = array("blah blah blha" => 12345); $object->{"membersonly_" . $i} = new StdClass; $object->{"onlymember"} = array("blah blah blha" => 12345); unset($object->{"membersonly_". $i}); unset($object->{"member_" . $i}); unset($object->{"onlymember"}); } unset($object); } memstat(); waste_a_little_less_memory(1000000); memstat(); waste_lots_of_memory(10000); memstat(); 

Для меня выход:

 current memory usage: 73308 current memory usage: 74996 current memory usage: 506676 

[отредактировано, чтобы удалить больше участников объекта]

unset() не освобождает память, используемую переменной. Память освобождается, когда «сборщик мусора» (в кавычках, поскольку у PHP не было реального сборщика мусора до версии 5.3.0, просто программа, свободная от памяти, которая работала в основном на примитивах) считает нужным.

Кроме того, технически вам не нужно вызывать unset() поскольку переменная $object ограничена областью действия вашей функции.

Вот сценарий, демонстрирующий разницу. Я изменил memstat() чтобы показать разницу в памяти с момента последнего вызова.

 <?php function memdiff() { static $int = null; $current = memory_get_usage(); if ($int === null) { $int = $current; } else { print ($current - $int) . "\n"; $int = $current; } } function object_no_unset($iters) { $i = 0; $object = new StdClass; for (;$i < $iters; $i++) { $object->{"member_" . $i}= array("blah blah blha" => 12345); $object->{"membersonly_" . $i}= new StdClass; $object->{"onlymember"}= array("blah blah blha" => 12345); } } function object_parent_unset($iters) { $i = 0; $object = new StdClass; for (;$i < $iters; $i++) { $object->{"member_" . $i}= array("blah blah blha" => 12345); $object->{"membersonly_" . $i}= new StdClass; $object->{"onlymember"}= array("blah blah blha" => 12345); } unset ($object); } function object_item_unset($iters) { $i = 0; $object = new StdClass; for (;$i < $iters; $i++) { $object->{"member_" . $i}= array("blah blah blha" => 12345); $object->{"membersonly_" . $i}= new StdClass; $object->{"onlymember"}= array("blah blah blha" => 12345); unset ($object->{"membersonly_" . $i}); unset ($object->{"member_" . $i}); unset ($object->{"onlymember"}); } unset ($object); } function array_no_unset($iters) { $i = 0; $object = array(); for (;$i < $iters; $i++) { $object["member_" . $i] = array("blah blah blha" => 12345); $object["membersonly_" . $i] = new StdClass; $object["onlymember"] = array("blah blah blha" => 12345); } } function array_parent_unset($iters) { $i = 0; $object = array(); for (;$i < $iters; $i++) { $object["member_" . $i] = array("blah blah blha" => 12345); $object["membersonly_" . $i] = new StdClass; $object["onlymember"] = array("blah blah blha" => 12345); } unset ($object); } function array_item_unset($iters) { $i = 0; $object = array(); for (;$i < $iters; $i++) { $object["member_" . $i] = array("blah blah blha" => 12345); $object["membersonly_" . $i] = new StdClass; $object["onlymember"] = array("blah blah blha" => 12345); unset ($object["membersonly_" . $i]); unset ($object["member_" . $i]); unset ($object["onlymember"]); } unset ($object); } $iterations = 100000; memdiff(); // Get initial memory usage object_item_unset ($iterations); memdiff(); object_parent_unset ($iterations); memdiff(); object_no_unset ($iterations); memdiff(); array_item_unset ($iterations); memdiff(); array_parent_unset ($iterations); memdiff(); array_no_unset ($iterations); memdiff(); ?> 

Если вы используете объекты, убедитесь, что классы реализуют __unset() , чтобы позволить unset() правильно очищать ресурсы. Старайтесь избегать как можно большего использования классов переменной структуры, таких как stdClass или назначения значений членам, которые не находятся в вашем шаблоне класса, поскольку память, назначенная им, обычно не очищается должным образом.

PHP 5.3.0 и выше имеют лучший сборщик мусора, но по умолчанию он отключен. Чтобы включить его, вы должны вызвать gc_enable() один раз.

memory_get_usage() « Возвращает объем памяти в байтах, который в настоящее время выделяется для вашего PHP-скрипта ».

Это объем памяти, выделенной для процесса операционной системой, а не объем памяти, используемый назначенными переменными. PHP не всегда возвращает память обратно в ОС – но эта память все еще может быть повторно использована при назначении новых переменных.

Демонстрация этого проста. Измените конец своего скрипта на:

 memstat(); waste_lots_of_memory(10000); memstat(); waste_lots_of_memory(10000); memstat(); 

Теперь, если вы правы, и PHP на самом деле утечка памяти, вы должны увидеть, что использование памяти растет вдвое. Однако, вот фактический результат:

 current memory usage: 88272 current memory usage: 955792 current memory usage: 955808 

Это происходит из-за того, что память «освобождается» после первоначального вызова resource_lots_of_memory () повторно используется вторым вызовом.

За 5 лет работы с PHP я написал скрипты, которые обрабатывали миллионы объектов и гигабайт данных в течение нескольких часов, и скрипты, которые запускались в течение нескольких месяцев. Управление памятью PHP не очень велико, но это не так плохо, как вы это делаете.

memory_get_usage сообщает, сколько памяти php выделило из os. Он не обязательно соответствует размеру всех используемых переменных. Если php использует пиковое использование памяти, он может решить не возвращать неиспользованный объем памяти сразу. В вашем примере функция waste_a_little_less_memory отключает неиспользуемые переменные с течением времени. Таким образом, использование пиков относительно невелико. waste_lots_of_memory накапливает большое количество переменных (= много используемой памяти), прежде чем освобождать его. Таким образом, использование пиков намного больше.

Мое понимание memory_get_usage () заключается в том, что его вывод может зависеть от широкого спектра операционных систем и факторов версии.

Что еще более важно, отключение переменной не мгновенно освобождает ее память, освобождает ее от процесса и возвращает ее в операционную систему (опять же, характеристики этой операции зависят от операционной системы).

Короче говоря, вам, вероятно, потребуется более сложная настройка, чтобы посмотреть на утечки памяти.

Я не уверен в точности его работы на PHP, но на некоторых других языках объект, содержащий другие объекты, когда он установлен в null, по сути не устанавливает для остальных объектов значение null. Он завершает ссылку на эти объекты, но поскольку у PHP нет «сборки мусора» в смысле Java, под-объекты существуют в памяти до тех пор, пока они не будут удалены отдельно.

memory_get_usage () не возвращает непосредственное использование памяти, а хранит память для запуска процесса. В случае огромного массива unset ($ array_a) не будет выпускать память, а потреблять больше в соответствии с memory_get_usage () в моей системе …

 $array_a="(SOME big array)"; $array_b=""; //copy array_a to array_b for($line=0; $line<100;$line++){ $array_b[$line]=$array_a[$line]; } unset($array_a); //having this shows actually a bigger consume print_r($array_b); с $array_a="(SOME big array)"; $array_b=""; //copy array_a to array_b for($line=0; $line<100;$line++){ $array_b[$line]=$array_a[$line]; } unset($array_a); //having this shows actually a bigger consume print_r($array_b); 

echo memory_get_usage ();