Как исправить утечку памяти в PHP - PullRequest
17 голосов
/ 18 июня 2009

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

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

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

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

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

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

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

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

Ответы [ 8 ]

7 голосов
/ 18 июня 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";

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

5 голосов
/ 18 июня 2009

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

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

Насколько велик CSV-файл, который вы читаете? А какие объекты и виды обработки вы делаете для этого?

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

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

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

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

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

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

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

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

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

1 голос
/ 18 июня 2009

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

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

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

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

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

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

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

0 голосов
/ 28 октября 2010

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

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

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

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

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

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

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

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

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

...