Сила освобождения памяти в PHP

в программе PHP, я последовательно читаю кучу файлов (с file_get_contents), gzdecode их, json_decode результат, анализирую содержимое, выкидываю большую часть его и сохраняю около 1% в массив. К сожалению, с каждой итерацией (я перемещаюсь по массиву, содержащему имена файлов), кажется, потеряна некоторая потеря памяти (в соответствии с memory_get_peak_usage, около 2-10 МБ каждый раз). У меня есть двойной и триплексный мой код, я не храню незавершенные данные в цикле (и необходимые данные почти не превышают около 10 МБ в целом), но я часто переписываю (фактически, строки в массиве). По-видимому, PHP не освобождает память правильно, тем самым используя все больше и больше ОЗУ, пока не достигнет предела. Есть ли способ сделать принудительную сборку мусора? Или, по крайней мере, выяснить, где используется память?

Спасибо заранее, Дмитрий

Ответ 1

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

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

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

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

в python

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

в PHP

эквиваленты sprintf также прекрасны.

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

Ответ 2

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

Примечание. Для активации круглого справочного коллектора необходимо включить zend.enable_gc в вашем php.ini или вызвать gc_enable().

Ответ 3

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

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

Ответ 4

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

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

к

while (condition) {
  do_cool_stuff();
}

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

ИЗМЕНИТЬ

Я запустил этот быстрый тест и не видел увеличения использования памяти. Это приводит меня к мысли, что утечка не находится в 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;
}

Ответ 5

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

foreach( $x as &$y)

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

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

Ответ 6

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

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

НО.

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

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

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

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

Ответ 7

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

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

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

НТН

С.

Ответ 8

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

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