У меня есть приложение Django, которое демонстрирует странное поведение при сборке мусора. В частности, существует одна точка зрения, которая будет постоянно увеличивать размер виртуальной машины каждый раз, когда она вызывается, - до определенного предела, после чего использование снова сокращается. Проблема в том, что для достижения этой точки требуется значительное время, и фактически у виртуальной машины, на которой запущено мое приложение, недостаточно памяти для всех процессов FCGI, чтобы занять столько памяти, сколько они иногда делают.
Я провел последние два дня, исследуя это и изучая сборку мусора в Python, и я думаю, что я понимаю, что происходит сейчас - по большей части. При использовании
gc.set_debug(gc.DEBUG_STATS)
Затем для одного запроса я вижу следующий вывод:
>>> c = django.test.Client()
>>> c.get('/the/view/')
gc: collecting generation 0...
gc: objects in each generation: 724 5748 147341
gc: done.
gc: collecting generation 0...
gc: objects in each generation: 731 6460 147341
gc: done.
[...more of the same...]
gc: collecting generation 1...
gc: objects in each generation: 718 8577 147341
gc: done.
gc: collecting generation 0...
gc: objects in each generation: 714 0 156614
gc: done.
[...more of the same...]
gc: collecting generation 0...
gc: objects in each generation: 715 5578 156612
gc: done.
Таким образом, по существу, огромное количество объектов выделено, но первоначально перемещено в поколение 1, а когда генерация 1 сканируется во время того же запроса, они перемещаются в поколение 2. Если я выполняю gc.collect вручную (2 ) после этого они удаляются. И, как я уже упоминал, там также удаляется, когда происходит следующая автоматическая развертка поколения 2, которая, если я правильно понимаю, будет в этом случае примерно как каждые 10 запросов (на данный момент приложению требуется около 150 МБ).
Хорошо, поэтому сначала я подумал, что в процессе обработки одного запроса могут происходить циклические ссылки, которые не позволяют собирать какие-либо из этих объектов при обработке этого запроса. Тем не менее, я потратил часы, пытаясь найти один из них с использованием pympler.muppy и objgraph, как после, так и путем отладки внутри обработки запроса, и, похоже, их нет. Скорее, кажется, что 14.000 объектов или около того, которые создаются во время запроса, все находятся в цепочке ссылок на некоторый глобальный объект запроса, то есть, как только запрос уходит, их можно освободить.
В любом случае, это была моя попытка объяснить это. Однако, если это так, и циклических зависимостей действительно нет, разве не должно быть освобождено все дерево объектов, если какой-либо объект запроса, который их удерживает, исчезнет, без участия сборщика мусора, чисто на основании количества ссылок падает до нуля?
С этой настройкой, вот мои вопросы:
Имеет ли вышесказанное смысл, или мне нужно искать проблему в другом месте? Является ли просто несчастным случаем, что важные данные хранятся так долго в этом конкретном случае использования?
Могу ли я что-нибудь сделать, чтобы избежать этой проблемы. Я уже вижу некоторый потенциал для оптимизации представления, но, похоже, это решение с ограниченной областью действия - хотя я не уверен, какой я бы был универсальный; Насколько целесообразно, например, вызвать gc.collect () или gc.set_threshold () вручную?
С точки зрения работы самого сборщика мусора:
Правильно ли я понимаю, что объект всегда перемещается в следующее поколение, если развертка смотрит на него и определяет, что ссылки на него не являются циклическими , но на самом деле их можно проследить до корневой объект.
Что произойдет, если gc выполняет, скажем, развертку 1-го поколения и находит объект, на который ссылается объект в поколении 2; следует ли это отношение внутри поколения 2, или оно ожидает, что произойдет развертка поколения 2, прежде чем анализировать ситуацию?
При использовании gc.DEBUG_STATS я забочусь прежде всего об информации "объекты в каждом поколении"; тем не менее, я продолжаю получать сотни «gc: 0.0740s истек», «gc: 1258233035.9370s истек». Сообщения; они совершенно неудобны - им требуется много времени для распечатки, и они затрудняют поиск интересных вещей. Есть ли способ избавиться от них?
Я не думаю, что есть способ сделать gc.get_objects () по поколениям, то есть получить только объекты из поколения 2, например?