Память освобождения силы в PHP

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

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

Там какой-либо путь состоит в том, чтобы сделать принудительную сборку "мусора"? Или, по крайней мере, для обнаружения, где память используется?

53
задан Sᴀᴍ Onᴇᴌᴀ 12 April 2018 в 06:13
поделиться

6 ответов

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

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

13
ответ дан 7 November 2019 в 08:40
поделиться

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

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

to

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;
}
9
ответ дан 7 November 2019 в 08:40
поделиться

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

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

19
ответ дан 7 November 2019 в 08:40
поделиться

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

foreach( $x as &$y)

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

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

6
ответ дан 7 November 2019 в 08:40
поделиться

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

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

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

HTH

C.

4
ответ дан 7 November 2019 в 08:40
поделиться

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

Рассмотрим две строки, конкатенированные в одну строку. Каждый оригинал должен оставаться до тех пор, пока не будет создан выход. Выходная строка длиннее, чем входная.
Поэтому для хранения результата такой конкатенации должно быть выделено новое место. Исходные строки освобождаются, но это небольшие блоки памяти.
В случае '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, в основном всегда является "последним" битом памяти в виртуальной карте, которую пришлось использовать. Поэтому, поскольку она постоянно растет, она сообщает о быстром росте. Поскольку каждое выделение приходится "на конец" используемого в данный момент блока памяти.

38
ответ дан 7 November 2019 в 08:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: