Принудительное освобождение памяти в PHP - PullRequest
52 голосов
/ 17 марта 2010

В программе PHP я последовательно читаю кучу файлов (с file_get_contents), gzdecode их, json_decode результат, анализирую содержимое, выбрасываю большую часть и сохраняю около 1% в массив.

К сожалению, при каждой итерации (я перебираю массив, содержащий имена файлов), кажется, что часть памяти теряется (согласно memory_get_peak_usage, каждый раз около 2-10 МБ). Я дважды и трижды проверил свой код; Я не храню ненужные данные в цикле (а необходимые данные едва ли превышают около 10 МБ в целом), но я часто переписываю (на самом деле, строки в массиве). Очевидно, PHP не освобождает память правильно, поэтому использует все больше и больше ОЗУ, пока не достигнет предела.

Есть ли способ принудительной сборки мусора? Или, по крайней мере, выяснить, где используется память?

Ответы [ 8 ]

37 голосов
/ 29 июня 2010

это связано с фрагментацией памяти.

Рассмотрим две строки, соединенные в одну строку. Каждый оригинал должен оставаться до тех пор, пока не будет создан вывод. Выходной сигнал длиннее любого из входных данных.
Следовательно, новое распределение должно быть сделано для хранения результата такой конкатенации. Исходные строки освобождаются , но они представляют собой небольшие блоки памяти.
В случае 'str1' . 'str2' . 'str3' . 'str4' у вас есть несколько темпов, создаваемых в каждом. - и ни один из них не помещается в пространство, которое было освобождено. Скорее всего, строки не располагаются в смежной памяти (то есть каждая строка есть, но различные строки не располагаются вплотную) из-за других видов использования памяти. Таким образом, освобождение строки создает проблему, поскольку пространство не может быть эффективно использовано повторно. Таким образом, вы растете с каждым созданным TMP. И вы ничего не используете повторно, никогда.

Используя имплозу на основе массива, вы создаете только 1 выходной сигнал - именно ту длину, которая вам требуется. Выполняется только 1 дополнительное выделение. Таким образом, он намного эффективнее использует память и не страдает от фрагментации конкатенации. То же самое относится и к питону. Если вам нужно объединить строки, более чем одна конкатенация всегда должна быть основана на массиве:

''.join(['str1','str2','str3'])

в питоне

implode('', array('str1', 'str2', 'str3'))

в PHP

эквиваленты sprintf также хороши.

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

19 голосов
/ 17 марта 2010

В PHP> = 5.3.0 вы можете вызвать gc_collect_cycles() для принудительной передачи GC.

Примечание. Вам необходимо включить zend.enable_gc в вашем php.ini или вызвать gc_enable(), чтобы активировать циклический сборщик ссылок.

12 голосов
/ 17 марта 2010

Нашел решение: это была конкатенация строк. Я генерировал входные данные построчно, объединяя некоторые переменные (вывод - файл CSV). Однако PHP, похоже, не освобождает память, используемую для старой копии строки, тем самым эффективно забивая ОЗУ неиспользованными данными. Переключение на подход, основанный на массиве (и добавление запятых непосредственно перед отправкой в ​​выходной файл) обошло это поведение.

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

9 голосов
/ 17 марта 2010

Я обнаружил, что менеджер внутренней памяти PHP, скорее всего, будет вызван после завершения функции. Зная это, я реорганизовал код в цикле примерно так:

while (condition) {
  // do
  // cool
  // stuff
}

до

while (condition) {
  do_cool_stuff();
}

function do_cool_stuff() {
  // do
  // cool
  // stuff
}

EDIT

Я запустил этот быстрый тест и не увидел увеличения использования памяти. Это наводит меня на мысль, что утечка не в json_decode()

for($x=0;$x<10000000;$x++)
{
  do_something_cool();
}

function do_something_cool() {
  $json = '{"a":1,"b":2,"c":3,"d":4,"e":5}';
  $result = json_decode($json);
  echo memory_get_peak_usage() . PHP_EOL;
}
6 голосов
/ 07 августа 2013

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

СИТУАЦИЯ: Я писал из запроса базы данных в CSV-файлы. Я всегда выделял одну строку $, а затем переназначал ее на следующем шаге. Сброс строки $ не помог; размещение строки размером 5 МБ в $ row первым (во избежание фрагментации) не помогло; создание массива $ row-s (загрузка в него множества строк + сброс всего объекта на каждом 5000-м шаге) не помогло; действительно попробовал пару вещей.

НО.

Когда я сделал отдельную функцию, которая открывает файл, передает 100 000 строк (этого достаточно, чтобы не поглотить всю память) и закрывает файл, ТО я сделал последующие вызовы этой функции (добавление в существующий файл), я обнаружил, что для каждой функции выхода PHP удаляет мусор. Это была вещь локального переменного пространства.

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

Это правило, насколько я понял. Однако отметим только одно: когда я пытался заставить мою функцию do_only_a_smaller_subset () получить некоторые переменные по ссылке (а именно, объект запроса и указатель файла), сборка мусора не произошла. Теперь, может быть, я что-то неправильно понимаю, и, возможно, объект запроса (mysqli) просочился, ну, я не знаю. Однако, поскольку он был передан ref, очевидно, он не мог быть очищен, поскольку существовал после точки выхода небольшой функции.

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

6 голосов
/ 17 марта 2010

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

foreach( $x as &$y)

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

Хорошая статья об утечках памяти PHP и их обнаружении на IBM

4 голосов
/ 17 марта 2010

Я собирался сказать, что не обязательно ожидать, что gc_collect_cycles () решит проблему - так как предположительно файлы больше не отображаются в zvars. Но вы проверяли, что gc_enable вызывался перед загрузкой каких-либо файлов?

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

Я полагаю, что одним из обходных путей может быть не использование file_get_contents, а скорее fopen () .... fgets () ... fclose (), а не отображение всего файла в памяти за один раз. Но вам нужно попробовать это подтвердить.

НТН

С

3 голосов
/ 09 ноября 2010

Недавно была похожая проблема с System_Daemon . Сегодня я выделил свою проблему на file_get_contents.

Не могли бы вы попробовать вместо этого fread? Я думаю, что это может решить вашу проблему. Если это произойдет, возможно, пришло время сообщить об ошибке в PHP.

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