Как работает сборщик мусора в PHP

У меня есть PHP-скрипт с большим количеством людей, он получает их данные из внешнего ресурса через SOAP, изменяет данные и отправляет их обратно. Из-за размера деталей я увеличил память PHP до 128 МБ. Примерно через 4 часа работы (вероятно, это займет 4 дня) закончилась память. Вот основы того, что он делает:

$people = getPeople();
foreach ($people as $person) {
    $data = get_personal_data();
    if ($data == "blah") {
        importToPerson("blah", $person);
    } else {
        importToPerson("else", $person);
    }
}

После того, как ему не хватило памяти и произошел сбой, я решил инициализировать $data перед циклом foreach, и, согласно top, использование памяти для процесса не превысило 7,8%, и он работает уже 12 часов.

Итак, мой вопрос: не запускает ли PHP сборщик мусора для переменных, инициализированных внутри цикла, даже если они используются повторно? Система восстанавливает память, а PHP еще не пометил ее как пригодную для использования и в конечном итоге снова выйдет из строя (сейчас я увеличил ее до 256 МБ, поэтому я изменил 2 вещи и не уверен, что это исправило, я, вероятно, мог бы изменить свой script обратно, чтобы ответить на этот вопрос, но не хотите ждать еще 12 часов, пока он сломается, чтобы выяснить это)?

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

РЕДАКТИРОВАТЬ: На самом деле у меня нет проблем со сценарием или тем, что он делает. На данный момент, что касается всех системных отчетов, у меня нет никаких проблем. Этот вопрос касается сборщика мусора и того, как/когда он освобождает ресурсы в цикле foreach и/или как система сообщает об использовании памяти процессом php.


person Rudiger    schedule 02.04.2012    source источник
comment
Мне интересно услышать, почему за это дважды проголосовали...   -  person Moses    schedule 03.04.2012
comment
Что происходит в importToPerson()?   -  person PeeHaa    schedule 03.04.2012
comment
Разве if ($data = "blah") { не должно быть if ($data == "blah") {?   -  person PeeHaa    schedule 03.04.2012
comment
пока скрипт не завершится, сборщик мусора php должен оставить его в покое, вам нужно управлять использованием памяти внутри скрипта.   -  person    schedule 03.04.2012
comment
@RepWhoringPeeHaa Не беспокойтесь о приведенном выше сценарии, это всего лишь основа, чтобы понять, что я инициализировал переменную внутри цикла foreach и имел проблемы с памятью, инициализация вне цикла foreach, похоже, остановила проблему, но не зная больше о как работает сборщик мусора, я не могу быть уверен.   -  person Rudiger    schedule 03.04.2012
comment
@Dagon Хорошо, из того, что я вижу, мне кажется, что я справился с использованием памяти, но я недостаточно знаю о том, как PHP и система восстанавливают память, чтобы быть уверенным.   -  person Rudiger    schedule 03.04.2012
comment
Возможно, взгляните на эту справочную страницу: php.net/manual/en/features.gc. php   -  person s1lence    schedule 03.04.2012
comment
@ Дагон, это неправда.   -  person dqhendricks    schedule 03.04.2012
comment
php.net/manual/en/features.gc.php   -  person dqhendricks    schedule 03.04.2012
comment
если вашему скрипту не хватает памяти, скорее всего, что-то в скрипте создает новые объекты или скаляры, которые никогда не обнуляются и не используются повторно для чего-то другого. или у вас работает старая версия PHP. вы можете использовать XDebug, чтобы увидеть, в чем проблема.   -  person dqhendricks    schedule 03.04.2012
comment
derickrethans.nl/xdebug-and-tracing-memory-usage.html   -  person dqhendricks    schedule 03.04.2012
comment
@dqhendricks Я просмотрел это и попытался заставить его работать, но по какой-то причине не смог (может быть, из-за конфигурации, я включил его, так что это было не так). Я также подумал, что это может вызвать больше проблем, чем решить, и инициализация переменных вне цикла, похоже, исправила это, что я подумал, что было странно, не зная достаточно о сборщике мусора.   -  person Rudiger    schedule 03.04.2012
comment
@dqhendricks Не хватало памяти, но я исправил это, выполнив инициализацию вне цикла. На самом деле это не помощь, это не рабочий вопрос, а скорее вопрос о том, почему этот рабочий вопрос.   -  person Rudiger    schedule 03.04.2012
comment
@Rudiger Ссылка, которую я разместил, объясняет, как работает php gc, поэтому я подумал, что это будет полезно. Что касается того, почему это сработало, то не должно, если только вы не показываете нам что-то. какую версию PHP вы используете?   -  person dqhendricks    schedule 03.04.2012
comment
@dqhendricks 5.2.17 Это пользовательский файл с iuscommunity.org (это сделал системный администратор, мало что об этом знает), потому что нам нужно использовать mcrypt. Я знаю, что это, вероятно, не должно работать, поэтому вопрос, потому что из всех отчетов, которые я могу сделать на данный момент, это работает.   -  person Rudiger    schedule 03.04.2012


Ответы (2)


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

В большинстве случаев, когда странице не хватает памяти, а предел довольно высок (а 128 МБ не так уж и много), возникает проблема с алгоритмом. Многие PHP-программисты собирают структуру данных, а затем передают ее на следующий шаг, который перебирает структуру, обычно создавая еще одну. Вспеньте, промойте, повторите. К сожалению, этот подход потребляет много памяти, и в итоге вы создаете несколько копий своих данных в памяти. Двумя действительно большими изменениями в PHP 5 были то, что объекты стали считаться ссылками, а не копироваться, и вся строковая подсистема была сделана намного быстрее. Но это все еще проблема.

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

Тем не менее, вы можете использовать этот подход для экономии памяти для части данных. Хитрость заключается в том, что вы явно unset() ключевую переменную или две в конце цикла. Это должно освободить пространство. Другая «лучшая практика» заключается в том, чтобы выйти из цикла обработки данных, которые не должны быть в цикле. Как вы, кажется, обнаружили.

Я запускал сценарии PHP, которым требуется более 1 ГБ памяти. Фактически, вы можете установить ограничение памяти для каждого скрипта с помощью ini_set('memory_limit', '1G');

person staticsan    schedule 02.04.2012
comment
В PHP 5.3 добавлен настоящий сборщик мусора. Это все еще несовершенно, но это улучшение по сравнению с тем, что вы описываете. - person ; 03.04.2012
comment
На самом деле он запускается из командной строки. Хотя я думал о том, чтобы каждый раз делать строку (а не все строки и перебирать возвращаемый массив), я чувствовал, что дополнительные запросы к базе данных сведут на нет любую пользу от этого. - person Rudiger; 03.04.2012
comment
Вполне вероятно иметь огромный цикл для обработки каждой строки, но у вас могут легко возникнуть проблемы с ресурсами при попытке выполнить новый запрос, когда вы все еще находитесь в процессе получения результатов предыдущего. Кроме того, иногда быстрее выполнить много небольших SQL-запросов, чем меньше тяжелых. - person staticsan; 03.04.2012
comment
Автоматические триггеры сборщика мусора цикла PHP не очень умны, поэтому, если вы делаете что-то, что может привести к тому, что большие объекты ссылаются друг на друга без ссылки откуда-либо, вам следует вызвать gc_collect_cycles() после возможной утечки памяти (php.net/manual/en/function.gc-collect-cycles.php). Это приводит к дополнительной загрузке ЦП, но снижает использование памяти. В противном случае PHP мог бы очистить мусор намного позже в будущем, и в то же время использование вашей памяти намного выше, чем ожидалось. - person Mikko Rantalainen; 01.02.2013

Используйте memory_get_usage(), чтобы узнать, что происходит? Можно поместить его внутрь цикла, чтобы увидеть поведение при распределении памяти. Вы пытались посмотреть на системный монитор или что-то еще, чтобы увидеть, сколько памяти php использует во время этого процесса?

person Norm    schedule 02.04.2012
comment
Не хочу изменять скрипт atm, так как нам придется начинать его снова с нуля (уже день насмарку). Наверху находится системный монитор, и использование памяти не превысило 7,8%, поэтому теоретически больше не будет выделений памяти, и сценарий не должен исчерпать память. - person Rudiger; 03.04.2012
comment
А нельзя было запустить другой скрипт с этими модификациями с ограничением на количество итераций? то есть я не совсем уверен, что вы делаете. - person Norm; 03.04.2012
comment
Сценарий на самом деле довольно интенсивный процесс, поскольку он выполняет много запросов SOAP / манипулирует базой данных. Если я не смогу получить ответ, я займусь расследованием немного позже, но это, вероятно, займет день, чтобы изменить сценарий, запустить его в течение часа или около того, контролировать выделение памяти, промывать и повторять. Надеялся, что кто-то более осведомленный, чем я, о сборщике мусора PHP даст некоторое представление. - person Rudiger; 03.04.2012