Почему этот простой скрипт php пропускает память? - PullRequest
15 голосов
/ 18 июля 2009

В надежде избежать будущих утечек памяти в программах 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

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

Ответы [ 6 ]

34 голосов
/ 18 июля 2009

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().

23 голосов
/ 18 июля 2009

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

Это связано с тем, что память "освобождается" после первоначального вызова waste_lots_of_memory () повторно используется вторым вызовом.

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

3 голосов
/ 18 июля 2009

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

2 голосов
/ 18 июля 2009

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

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

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

0 голосов
/ 13 января 2014

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 ();

0 голосов
/ 18 июля 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...