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

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

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

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

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

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

Обнаружена причина проблемы

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

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


person thomasrutter    schedule 18.06.2009    source источник
comment
+1 Я много работаю с CSV-файлами на PHP и в конце концов столкнусь с этой проблемой.   -  person alex    schedule 18.06.2009
comment
У меня была аналогичная проблема в CodeIgniter, обрабатывая большой файл журнала и вставляя его в БД. Я изменил вставки на использование CodeIgniters simple_query вместо обычного query метода и уменьшил использование памяти в 10 раз.   -  person Matthew Smith    schedule 03.05.2011


Ответы (8)


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

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

Насколько велик CSV-файл, который вы читаете? И какие объекты и какую обработку вы с ним делаете?

person lpfavreau    schedule 18.06.2009
comment
Спасибо за предложение xdebug. Теперь я нашел причину :) - person thomasrutter; 18.06.2009

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

  • Измените memory_limit на что-нибудь маленькое, например 500 КБ
  • Закомментируйте все шаги обработки, кроме одного, которые применяются к каждой строке.
  • Запустите ограниченную обработку всего файла 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";

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

person too much php    schedule 18.06.2009
comment
Спасибо за предложение. Это хорошее предложение для тех, у кого есть подобная проблема, но я случайно обнаружил, что функция трассировки xdebug наиболее полезна для поиска причины здесь. - person thomasrutter; 18.06.2009

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

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

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

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

person Vinko Vrsalovic    schedule 18.06.2009

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

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

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

person stefs    schedule 18.06.2009

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

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

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

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

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

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

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

person UnkwnTech    schedule 18.06.2009
comment
Я использовал fgetcsv, извините, что забыл упомянуть. Я был почти уверен, что проблема не в чтении файла, потому что сам файл относительно невелик. - person thomasrutter; 18.06.2009

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

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

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

person Jani Hartikainen    schedule 18.06.2009
comment
Он читает CSV по одной строке за раз. Весь CSV занимает всего пару мегабайт; кажется, проблема заключается в другой обработке. - person thomasrutter; 18.06.2009
comment
разве они не называются круговыми ссылками? - person stefs; 18.06.2009
comment
@Schnalle абсолютно правильно. Я был слишком сонным, чтобы вспомнить правильный термин =) - person Jani Hartikainen; 18.06.2009
comment
Сборщик мусора в PHP 5.3 должен иметь дело с циклическими ссылками. В старых версиях PHP это действительно проблема. - person Mark Rose; 17.01.2012

Можете ли вы изменить свой memory_limit в своем php.ini?

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

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

person alex    schedule 18.06.2009
comment
Хорошее предложение, но предел памяти уже составляет 128 МБ или около того, и увеличение его только купит способность работать немного дольше - в конечном итоге я бы хотел иметь возможность импортировать больше, чем в 10, может быть, в 50 раз больше. размер. - person thomasrutter; 18.06.2009
comment
Да, я понимаю, что это не идеальное решение. Просто подумал, что упомяну об этом. - person alex; 18.06.2009

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

person captainclam    schedule 28.10.2010