Статьи

Лучшее понимание сборки мусора в PHP

Интересно, как всего несколько лет могут изменить имена, которые даны вещам. Если бы это появилось сегодня, это, вероятно, назвали бы опциями рециркуляции PHP, потому что вместо того, чтобы собирать вещи и выбрасывать их на свалку, где их больше никогда не увидят, мы действительно говорим о захвате вещей, использование которых прошло, и настроить их, чтобы быть полезным снова. Но, когда идея была разработана, рециркуляция не была ле-чери общества, и этой задаче было дано вульгарное название «Сбор мусора». Что мы можем сделать, кроме как следовать тому, что нам дала история и обычное использование?

Программа генерирует мусор

Программы используют ресурсы; иногда маленькие, иногда намного больше. Примером может служить поле данных. Программа может определять поле данных, скажем порядковый номер, которое используется в программе. И после определения это поле данных займет место в памяти, вероятно, всего несколько байтов, но, тем не менее, пространство. Поскольку каждая машина или среда программирования имеет ограниченный (хотя и большой) объем доступного пространства, оставшееся пространство, которое она оставит, будет уменьшено на количество места, которое занимает это поле.

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

Я написал несколько таких программ в свое время. Они были прекрасными, и мне всегда было приятно, когда все остальные в магазине заметили, что я их создал. Ничто так не указывает на ваши возможности, как если бы вы сами поставили большой кусок железа IBM на стоянку, в то время как из окружающих кабинетов один человек за другим громко говорит: «Эй, что-то не так с система? »Хитрость в том, чтобы звонить вторым или третьим, чтобы отвлечь внимание от себя.

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

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

Первый уровень — конец области

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

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

Второй уровень — подсчет ссылок

Во-вторых, как и большинство языков сценариев, PHP отслеживает, сколько объектов используют данную переменную, используя технику, называемую подсчетом ссылок.

Когда переменная создается в скрипте PHP, PHP создает небольшой «контейнер», называемый zval, который состоит из значения, присвоенного этой переменной, плюс двух других фрагментов информации: is_ref и refcount. Контейнеры zval хранятся в таблице, в которой имеется одна таблица для каждой области действия (сценарий, функция, метод и т. Д.).

is_ref — это простое значение true / false, которое указывает, является ли переменная частью набора ссылок, что помогает PHP определить, является ли это простая переменная или ссылка.

Refcount более интересен тем, что содержит числовое значение, указывающее, сколько разных переменных используют это значение. То есть, если вы определите переменную $dave = 6 , refcount будет установлен в 1. Если я тогда скажу $programmer = $dave , refcount будет увеличен до 2. PHP знает достаточно, чтобы не создавать второй zval для значения 6; он просто обновляет счетчик в уже существующем контейнере значений. Когда программа заканчивается, или когда мы покидаем область действия функции, или когда используется unset() , тогда этот счет будет уменьшен. Когда refcount достигает нуля, zval уничтожается, и любая память, которую он держал, теперь свободна.

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

Однако возникает проблема, если мы используем массив в другом массиве, что происходит с некоторой частотой в более сложных сценариях PHP. В этом случае refcount для значения массива устанавливается равным 1, когда задается исходное значение массива, а затем увеличивается до 2, когда массив связан с другим массивом. Если область использования второго массива затем заканчивается, тогда refcount уменьшается на 1. Сейчас мы находимся в ситуации, когда само значение больше не связано ни с чем, но контейнер (zval), который представляет его, все еще имеет refcount больше нуля.

Конечным результатом является то, что хранилище, представленное исходным массивом, не будет освобождено, и этот объем памяти теперь недоступен для использования кем-либо. Обычно мы думаем об этом количестве потерянного хранилища как о маленьком, но часто это не так. Массивы могут быть очень большими вещами сегодня, и это особенно проблематично, если скрипт, в котором это происходит, является демоном или другой почти непрерывно работающей функцией. В этом случае результирующая «утечка памяти» может иметь разрушительные последствия для производительности и даже для работы сервера.

Третий уровень — официальная сборка мусора

Очевидно, что очистки, ориентированные на подсчет ссылок, имеют свои ограничения, но, к счастью, PHP 5.3 предложил другой вариант, чтобы помочь в этой ситуации.

Конкретная ситуация, к которой мы хотим обратиться в нашем цикле мусора, — это случай, когда zval был уменьшен, но это все еще ненулевое значение. По сути, цикл видит, какие значения можно уменьшить, а затем освободить те, которые стремятся к нулю.

Что действительно происходит, так это то, что PHP отслеживает все корневые контейнеры (zvals). Это делается независимо от того, включена ли сборка мусора (потому что это быстрее сделать, а не спрашивать, включена ли сборка мусора, yada, yada, yada). Этот корневой буфер содержит до 10000 корней (фиксированный размер, но его можно изменить). Когда он заполняется, запускается механизм сборки мусора, и он начинает анализировать этот буфер.

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

Затем он проходит снова и помечает (на этот раз небольшой волнистой линией) все звали, чье уменьшенное число равно нулю. Те, которые не равны нулю, увеличиваются, чтобы они возобновили свои исходные значения.

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

Сборка мусора всегда zend.enable_gc в PHP, но вы можете отключить ее в файле php.ini с помощью директивы zend.enable_gc . Или вы можете сделать это в своем скрипте, вызвав функции gc_enable() и gc_disable() .

Как отмечалось выше, сборка мусора, если она включена, запускается при заполнении корня, но вы можете переопределить это и запустить gc_collect_cycles() когда захотите, с помощью функции gc_collect_cycles() . И вы можете изменить размер корневого буфера с gc_root_buffer_max_entries значения gc_root_buffer_max_entries значении zend/zend_gc.c в исходном коде PHP.

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

Когда вы должны его использовать

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

Во-первых, имейте в виду, что до тех пор, пока вы не запустите его (с помощью функции gc_collect_cycles() ), формальная сборка мусора не произойдет до тех пор, пока корневая таблица (10 000 записей) не заполнится, и поскольку эта таблица находится на уровне области действия, то есть не случится для небольших функций.

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

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

Изображение через Fotolia