Php очистить память после вызова функции

Обновлено: 07.07.2024

У меня есть два простых вопроса. Что лучше/полезно для очистки памяти.

У меня есть одна функция с одним циклом. Я получаю (через несколько минут)

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

А как насчет вызовов функций в цикле? Будет ли PHP выпускать все распределения в этих функциях? (После вызова)

And what about function calls in cycle? Will PHP release all allocations in these functions?(after call)

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

Сам PHP иногда смешивает обе концепции, но, как правило, переменная, установленная в NULL, не совпадает с переменной, которая не существует:

Я нашел проблему.

Сначала это было вызвано xdebug профилирующими инструментами (я включил все:)) - и он потребляет много памяти.

Итак, помните: xdebug (при включении профилирования) потребляет некоторую память в процессе PHP вашего приложения

Во-вторых, я не выпустил статические элементы, используемые в вызываемых функциях.

unset() делает именно это, он отключает переменную; но он не освобождает память сразу.

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

И помните, что unset не обязательно будет выпускать память, используемую переменной, если у вас есть другие ссылки на эту переменную. Он просто удалит ссылку и уменьшит счетчик ссылок для фактических сохраненных данных на 1.

ИЗМЕНИТЬ
В то время как unset не сразу освобождает используемую память (на самом деле это делает только сборка мусора), память, которая больше не используется в качестве результата, доступна для объявления новых переменных

Если вы отключили переменную, она просто помечена, поэтому в следующей сборке мусора она будет удалена. Если параметр равен нулю, данные переменной будут перезаписаны.

Возможно, см. также комментарии к руководству php: Unset Manual

Встроенный драйвер MySQL (mysqlnd) управляет памятью по-другому, в отличие от клиентской библиотеки MySQL (libmysql). Библиотеки различаются способом выделения и освобождения памяти, тем, как память выделяется по кускам во время чтения результатов из MySQL, существующими опциями для отладки и разработки, и тем, как результаты, считанные из MySQL, связаны с пользовательскими переменными PHP.

Следующая информация предназначена в качестве введения и обобщения для пользователей, заинтересованных в понимании mysqlnd на уровне C-кода.

Используемые функции для управления памятью

Все операции выделения и освобождения памяти происходят используя PHP-функции, предназначенные для управления памятью. Поэтому, потребление памяти встроенного драйвера MySQL может быть отслежено при помощи вызовов PHP API, таких как memory_get_usage() . Из-за того, что память выделяется и освобождается при помощи системы управления памятью PHP, изменения на уровне операционной системы могут быть видны не мгновенно. Система управления памятью PHP ведёт себя как прокси, которая может вызывать задержку в освобождении памяти. Ввиду этого, сравнение использования памяти встроенного драйвера MySQL и клиентской библиотеки MySQL (libmysql) довольно сложно. Клиентская библиотека MySQL (libmysql) использует систему управления памятью операционной системы напрямую, следовательно, эффект на уровне операционной системы может быть виден незамедлительно.

Любое ограничение памяти, установленное в PHP, также влияет на встроенный драйвер MySQL. Это может вызвать ошибки переполнения памяти при извлечении больших массивов данных, которые превышают размер оставшейся памяти, предоставленных РНР. Из-за того, что клиентская библиотека MySQL не использует функций управления памяти PHP, она не подчиняется ограничению памяти, установленному в PHP. При использовании libmysql, в зависимости от модели развёртывания, объем памяти, занимаемый PHP-процессом, может вырасти за пределы ограничений, установленных в PHP. В тоже время, PHP-скрипты могут обрабатывать больший объем массивов данных, так как области памяти, выделенные для хранения данных, не находятся под управлением РНР.

Функции системы управления памятью PHP вызываются встроенным драйвером MySQL через легковесную обёртку. Среди прочего, обёртка делает отладку легче.

Обработка массивов полученных данных

Различные MySQL-сервера и различные клиентские API различают буферизированные и небуферизированные результаты. Небуферизированные результаты передаются строка за строкой от MySQL к клиенту и клиент читает их по порядку. Буферизированные результаты забираются клиентской библиотекой целиком до передачи их клиенту.

Встроенный драйвер MySQL использует PHP-потоки для сетевого общения с сервером MySQL. Результаты, посланные MySQL-сервером, выбираются из сетевых буферов PHP-потоков в результирующий буфер mysqlnd. Результирующий буфер состоит из zvals. На втором шаге результаты становятся доступными PHP-скрипту. Последняя передача из результирующего буфера в PHP-переменные вызывает потребление памяти и в большинстве случаев оно заметно при использовании буферизированных результатов.

По умолчанию встроенный драйвер MySQL пытается избежать двойного хранения буферного результата в памяти. Результаты хранятся только один раз во внутренних результирующих буферах и их zvals. Когда результаты забираются в РНР-переменные PHP-скриптом, переменные будут ссылаться на внутренние результаты буферов. Результаты запросов к базам данных не копируются и хранятся в памяти только один раз. Достаточно пользователю изменить содержимое переменной, содержащей результаты работы базы данных, как будет выполнен механизм копирования при записи (Copy-On-Write), для того, чтобы избежать изменения ссылающего внутреннего буфера результата. Содержимое буфера не должно быть изменено, так как пользователь может принять решение прочитать результат во второй раз. Механизм копирования при записи реализуется с помощью дополнительного управления списком ссылок и использования стандартных zval счётчиков ссылок. Копирование при записи также должно быть сделано, если пользователь читает данные результата в PHP переменных и освобождает данные результата прежде, чем переменные будут уничтожены.

В общем, этот шаблон работает хорошо для скриптов, которые читают наборы данных единожды и не изменяют переменных, содержащих результаты. Его главный недостаток в накладных расходах памяти, вызванных дополнительным управлением ссылками, причина которого в первую очередь связана с тем, что пользовательские переменные, удерживающие результаты, не могут быть полностью освобождены до того, как система управления ссылками mysqlnd содержит ссылки на них. Встроенный драйвер MySQL удаляет ссылку на на пользовательские переменные когда массив полученных данных освобождается или выполняется механизм копирования при записи. Наблюдатель увидит рост общего потребления памяти пока массив полученных данных не освободится. Используйте статистику, чтобы проверить, скрипт явно произвёл освобождение данных результата или же драйвер сделал это неявно и поэтому память используется в течение более долгого времени, чем это необходимо. Статистика также помогает увидеть количество операций копирования при записи.

PHP-скрипт, читающий множество небольших строк в буферизированном массиве данных, использующий код, подобный while ($row = $res->fetch_assoc()) < . >, может оптимизировать потребление памяти, запросив копии вместо ссылок. Хотя и запрос копий означает хранение тех же результатов в памяти дважды, это позволяет PHP уничтожить копию, содержащую в $row в качестве итерируемого массива данных и перед уничтожением результат устанавливает сам себя. На загруженном сервере оптимизация использования памяти может помочь улучшить общую производительность системы, хотя для отдельного скрипта подход с копией вместо ссылок может быть медленнее в связи с дополнительным выделением памяти и дополнительными операциями копирования в памяти.

Режим копирования может быть включён принудительно, установив mysqlnd.fetch_data_copy=1.

Контроль и отладка

Существует несколько методов отслеживания использования памяти во встроенном драйвере MySQL "mysqlnd". Если цель - получить быстрый высокоуровневый обзор или проверить эффективность PHP-скриптов при работе с памятью, то проверьте статистику, собранную библиотекой. Статистика позволит вам, например, поймать SQL-запрос, который генерирует больше результатов, чем обрабатываются PHP-скриптом.

Журнал отладки может быть сконфигурирован для записи вызовов системы управления памятью. Это помогает увидеть когда память выделяется и освобождается. Однако, размер запрошенных кусков памяти может не быть в списке.

В некоторых последних версиях встроенного драйвера MySQL "mysqlnd" присутствует возможность эмуляции случайных ситуаций нехватки памяти. Эта возможность была задумана для использования только C-разработчиками библиотеки или авторами плагина mysqlnd. Пожалуйста, используйте поиск по исходному коду для соответствующей настройки PHP и для дальнейшей информации. Это возможность является недокументированной и может быть изменена в любое время без дополнительного уведомления.

У меня есть два простых вопроса. Что лучше /полезно для очистки памяти.

У меня есть одна функция с одним циклом. Я получаю (через несколько минут)

Я устанавливаю null и unset () - каждый объект (в конце цикла), но все еще безуспешно :( Я не могу выяснить, что потребляет память.

А как насчет вызовов функций в цикле? Будет ли PHP освобождать все выделения в этих функциях? (После вызова)

Я нашел проблему.

Сначала это было вызвано xdebug инструментами профилирования (я включил все :)) - и это потребляет много памяти.

Помните: xdebug (при включенном профилировании) потребляет часть памяти в процессе PHP вашего приложения

Во-вторых, я не выпустил статические элементы , используемые в вызываемых функциях.

PHP сам иногда смешивает обе концепции, но, в общем случае, переменная, установленная в NULL, отличается от переменной, которая не существует:

unset () делает именно это, она отменяет переменную; но это не сразу освобождает память.

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

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

ИЗМЕНИТЬ Хотя unset не освобождает сразу используемую память (фактически, это делает только сборка мусора), память, которая больше не используется в результате, доступна для объявления новых переменных

Если вы сбросите переменную, она просто помечена, поэтому при следующей сборке мусора она будет удалена. Если установлено значение null, данные переменной перезаписываются.

Возможно, смотрите также комментарии к руководству по php: Удалить руководство

And what about function calls in cycle? Will PHP release all allocations in these functions?(after call)

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

введите сюда описание изображения

Проблема собственно вот в чем: мои скрипты php после отработки засоряют оперативную память сервера,но не все конечно. Системный администратор после изучения этой темы говорит что память засоряется после моих скриптов и они являются активными процессами( как я понял спящими по букве S) Я прекрасно понимаю, что вы не волшебники и не можете проинтуичить без выкладки кода, но может кто то сталкивался с подобными проблемами. Хотелось бы понять алгоритм нахождения ответа. Может он заключается в том чтобы прозвонить код на количество резервируемой под него памяти, найти самый прожорливый участок и оптимизировать его.

Даже если это будет выполнено, памяти будет жрать меньше, но причина похоже не в этом. Может необходимо найти конфигурационный файл сервера и обратить внимание на конкретные строки параметров? В зависимости от ситуации я добавлю сюда необходимую информацию

Или может добавлять какуюто строку кода в php скрипт чтобы сервер убивал соединение?

Да кстати в циклах использую

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

Откуда начинать копать? Статьи по оптимизации кода читал, применяю их на практике, но как известно в этом направлении можно далеко уйти и писать на Ассемблере

Добавлено минутой позже:

Также использую в коде

в том случае если код не имеет смысла дальше выполнять - или вместо него необходимо использовать

Добавлено 5 минутами позже:

Это веб сервер. Пользователь загружает фаил эксель для парсинга и получается вышеописанное NGINX 1.6.2 PHP5-FPM без APACHE 5.6 PHP версия

Добавлено через 9 часов:

на форуме говорится о команде

которая показывает что хочет выполнить процесс в нашем случае PID 10354. Это конечно не ответ на вопрос, но может поспособствовать его решению.

На ресурсе сказано что данный режим может быть вызван недостатком мощностей процессора(но по сути какой бы мощный процессор не был, нагрузить его на 100% можно), также сказано что такое поведение наблюдается когда в коде php есть команда

Да - данная команда ввергает процесс в состояние Sleep, но здесь нет зависимости от количества потребляемой памяти.

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

Выполнялся скрипт, который отожрал 28М оперативки (см. вывод top -u www-data).

Собственно, скрипт выполнен, результаты получены. Как теперь заставить скрипт освободить ресурсы? А то он так и висит в задачах, занимаю кучу оперативки.

Upd: в apache2.conf уменьшил время KeepAliveTimeout - вроде помогло. Это нормальное решение, или костыль?



это слишком радикально.


Как теперь заставить скрипт освободить ресурсы

имхо вы что-то делаете не так.


Насколько я понимаю, процесс, в котором выполнялся этот скрипт, остается работать, так как к серверу поступают какие-то запросы. Как я написал выше, если уменьшить в настройках apache время keepAliveTimeout, то этот процесс завершается.

А вообще, не исключаю, что я в чем-то и ошибаюсь, но разве php не должен сам освобождать ресурсы?

но разве php не должен сам освобождать ресурсы?

должен.
а скрипт после работы должен завершаться


Это логично, но у меня этого, почему-то, не происходило. Во всяком случае, если верить top'у. Отсюда и возник вопрос.

скрипт кривой, не?


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

файл надеюсь закрываешь?


Естественно. Это было первое, что я проверил.

может, надо как-то в сторону сборщика мусора посмотреть?
//я не пхпшник


ну там garbage collector присутствует. В php.ini, если мне память не изменяет, есть какие-то настройки. Вроде должен работать.

Я тоже не особо php-шник. Но пришлось несколько скриптов наваять.


Как теперь заставить скрипт освободить ресурсы


попахивает очередным мемликом в пхп


Ну если не лень, то посмотрите. Буду признателен. Только не пинайте за быдлокодерство =)

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

Здесь $filepath - массив с именами файлов (сейчас файл только один, но достаточно большой - примерно 13К строк).

parseStr разделяет строку на отдельные элементы и возвращает массив (фактически, делает explode). В случае, если строка некорректна, возвращает false.

checkArr проверяет, есть ли указанный элемент (1 параметр) в массиве (2 параметр). Внутри нее используется in_array.

unset (tmp_arr) прикрутил уже в процессе доработок. Не уверен, что от него здесь есть толк.


events_arr - массив, созданный внутри функции. Насколько я понимаю, мне не нужно явно вызывать unset (events_arr). Он ведь сам должен уничтожиться по завершении функции, так?


А что в parseStr, может там утечка, еще было бы неплохо прогнать профайлером (xdebug, hxprof)


xdebug у меня даже поставлен, но я его так и не осилил.

А в parseStr что-то типа

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

Вообще-то процесс Апач потребляет столько памяти, сколько максимально понадобилось для выполнения скрипта. Если выполнить два скрипта, один из которых потребил 40МБ памяти, а другой - 15МБ, то размер процесса Апач будет потреблять 40МБ (скрипт) + 8МБ (сам Апач) = 48МБ (приблизительно).

MaxRequestsPerChild Directive Description: Limit on the number of requests that an individual child server will handle during its life

Только после перезагрузки дочернего процесса память будет освобождена.

Снижение времени KeepAlive может снижать потребление процессом Апач памяти путем уничтожения информации о соединении с клиентом (количество запросов в сессии, время последнего запроса и.т.д).

Поэтому, такая ситуация нормальна.

MaxMemFree Directive Description: Maximum amount of memory that the main allocator is allowed to hold without calling free()

The MaxMemFree directive sets the maximum number of free Kbytes that the main allocator is allowed to hold without calling free(). When not set, or when set to zero, the threshold will be set to unlimited.

Setting MaxRequestsPerChild to a non-zero value limits the amount of memory that process can consume by (accidental) memory leakage.


Вот это как-раз то, что было нужно.

Спасибо за объяснение и указание еще нескольких полезных директив.

Кучу раз пересматривал конфиги, но на MaxMemFree ни разу не обратил внимания. Буду внимательнее.

Читайте также: