Почему эта простая PHP скрипт утечка памяти?
В надежде избежать будущих утечек памяти в php-программах (модули drupal и т.д.) я возился с простыми сценариями php, которые утечка памяти.
Может ли эксперт php помочь мне найти, что об этом script приводит к тому, что использование памяти постоянно поднимается?
Попробуйте запустить его самостоятельно, изменив различные параметры. Результаты интересны. Вот он:
<?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
[отредактировано, чтобы удалить больше членов объекта]
Ответы
Ответ 1
unset()
не освобождает память, используемую переменной. Память освобождается, когда "сборщик мусора" (в кавычках, поскольку у PHP не было реального сборщика мусора до версии 5.3.0, просто программа, свободная от памяти, которая работала в основном на примитивах) считает нужным.
Кроме того, технически вам не нужно вызывать unset()
, так как переменная $object
ограничена областью действия вашей функции.
Вот script, чтобы продемонстрировать разницу. Я изменил вашу функцию 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()
один раз.
Ответ 2
memory_get_usage()
"Возвращает объем памяти в байтах, который в настоящее время выделяется для вашего PHP script."
Это объем памяти, выделенной для процесса операционной системой, а не объем памяти, используемый назначенными переменными. PHP не всегда возвращает память обратно в ОС, но эта память все еще может быть повторно использована при назначении новых переменных.
Демонстрация этого проста. Измените конец вашего script на:
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 не очень велико, но это не так плохо, как вы это делаете.
Ответ 3
memory_get_usage
сообщает, сколько памяти php выделено из os. Он не обязательно соответствует размеру всех используемых переменных. Если php использует пиковое использование памяти, он может решить не возвращать неиспользованный объем памяти сразу. В вашем примере функция waste_a_little_less_memory
отключает неиспользуемые переменные с течением времени. Таким образом, использование пиков относительно невелико. waste_lots_of_memory
накапливает большое количество переменных (= много используемой памяти), прежде чем освобождать его. Таким образом, использование пиков намного больше.
Ответ 4
Мое понимание memory_get_usage() заключается в том, что его вывод может зависеть от широкого спектра операционных систем и факторов версии.
Более важно то, что отмена переменной не мгновенно освобождает память, освобождает ее от процесса и возвращает ее в операционную систему (опять же, характеристики этой операции зависят от операционной системы).
Короче говоря, вам, вероятно, понадобится более сложная настройка для просмотра утечек памяти.
Ответ 5
Я не уверен в точности его работы в PHP, но на некоторых других языках объект, содержащий другие объекты, когда он установлен в null, по сути не устанавливает для остальных объектов значение null. Он завершает ссылку на эти объекты, но поскольку PHP не имеет "сбор мусора" в смысле Java, под-объекты существуют в памяти до тех пор, пока они не будут удалены индивидуально.
Ответ 6
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);
echo memory_get_usage();