Как исправить утечку памяти в PHP

мое приложение PHP имеет скрипт импорта, который может импортировать записи.

на данный момент он импортирует из CSV-файла. Он читает каждую строку CSV-файла, по одной строке за раз с помощью fgetcsv, и для каждой строки он делает много обработки этой записи, включая запросы к базе данных, а затем переход к следующей строке. Ему не нужно накапливать больше памяти.

после импорта около 2500 записей PHP умирает, говоря, что он запущен над своим пределом памяти (132 MB или около того).

сам файл CSV-это всего лишь пара мегабайт - другая обработка, которая происходит, делает много сравнений строк, различий и т. д. У меня есть огромное количество кода, работающего на нем, и было бы трудно придумать "самый маленький образец воспроизведения".

Каковы некоторые хорошие способы найти и исправить такую проблему?

причина проблемы найдена

у меня есть класс debug, который регистрирует все мои запросы к базе данных во время выполнения. Таким образом, эти строки SQL, длиной около 30 КБ, оставались в памяти. Я понимаю, что это не подходит для сценариев, предназначенных для работы в течение длительного времени.

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

8 ответов


Это поможет взглянуть на код, но если вы хотите отладить его самостоятельно, посмотрите на отладчик xdebug, это поможет профилировать ваше приложение.

конечно, в зависимости от того, что вы делаете, возможно, он накапливает некоторую память, хотя 132MB кажется уже высоким для 2500 записей. Конечно, вы можете настроить лимит памяти в php.ini, если нужно.

насколько велик CSV-файл, который Вы читаете? А какие предметы и вид обработка вы делаете с ним?


Если вы действительно подозреваете, что в вашем скрипте есть только одна или две утечки памяти, которые вызывают его сбой, то вы должны предпринять следующие шаги:

  • изменить memory_limit к чему-то небольшому, как 500KB
  • прокомментируйте все, кроме одного из шагов обработки, который применяется к каждой строке.
  • запустите ограниченную обработку по всему CSV-файлу и посмотрите, может ли она завершиться.
  • постепенно добавить больше шагов назад и посмотреть, если всплески использования памяти.

пример:

ini_set('memory_limit', 1024 * 500);
$fp = fopen("test.csv", 'r');
while($row = fgetcsv($fp)) {
    validate_row($row);         // step 1: validate
    // add these back in one by one and keep an eye on memory usage
    //calculate_fizz($row);     // step 2: fizz
    //calculate_buzz($row);     // step 3: buzz
    //triangulate($row);        // step 4: triangulate
}
echo "Memory used: ", memory_get_peak_usage(), "\n";

худший сценарий - это все ваших шагов обработки умеренно неэффективны, и вам нужно будет оптимизировать все из них.


Это зависит от того, как вы очищаете переменные после их завершения.

похоже, что вы закончили с записью, но вы все еще храните информацию где-то. Использовать unset () чтобы очистить переменные, если есть сомнения.

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

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


вы можете попробовать локальную установку php5.3 и позвоните http://www.php.net/manual/en/function.gc-collect-cycles.php.

gc_collect_cycles - заставляет сбор любых существующих циклов мусора

если ситуация улучшается, вы, по крайней мере, проверили (на) проблему(ы).


Как Вы читаете файл? Если вы используете fread / filegetcontents или другие такие функции, то вы собираетесь использовать весь размер файла (или сколько вы загружаете fread) в памяти, поскольку весь файл загружается во время вызова. Однако если вы используете fgetcsv если будет читать только одну строку за раз в зависимости от длины строки, это может быть драматически легче на вашей памяти.

также убедитесь, что вы повторно используете как можно больше переменных на каждом петля. Убедитесь, что в них нет массива с большими объемами данных.

в качестве последнего Примечания также убедитесь, что вы открываете файл перед циклом, а затем закрываете его после слов:

$fh = fopen(...);
while(true)
{
//...
}
fclose($fh);

вы действительно не хотите этого делать:

while(true)
{
$fh = fopen(...);
//...
fclose($fh);
}

и, как говорили другие, будет трудно сказать, не увидев какой-то код.


трудно сказать причину, не видя никакого кода. Однако типичной проблемой являются рекурсивные ссылки, т. е. объект A указывает на объект B и наоборот, что может привести к сбою GC.

Я не знаю, как вы в настоящее время обрабатываете файл, но вы можете попытаться прочитать только одну строку за раз. Если Вы читаете весь файл сразу, он может потреблять больше памяти.

Это на самом деле одна из причин, почему я часто предпочитаю Python для пакета задачи обработки.


вы можете изменить memory_limit в своем php.Ини?

кроме того, может ли выполнение unset($var) для переменных освободить некоторую память? Может ли $var = null помочь?

см. Также этот вопрос: что лучше при освобождении памяти с помощью PHP: unset () или $var = null


У меня была та же проблема, и это также было связано с профилированием базы данных (Zend_Db_Profiler_Firebug). В моем случае он протекал 1mb в минуту. этот сценарий должен был работать в течение нескольких дней, поэтому он рухнет в течение нескольких часов.