2009-11-16 3 views
9

У меня есть приложение Django, которое демонстрирует странное поведение коллекции мусора. Существует, в частности, один вид, который будет просто постоянно увеличивать размер виртуальной машины каждый раз, когда он будет вызван, - до определенного предела, и в этом случае использование снова падает. Проблема в том, что до достижения этой точки требуется значительное время, и на самом деле виртуальная машина, на которой работает мое приложение, не имеет достаточной памяти для всех процессов FCGI, которые занимают столько же памяти, сколько они иногда делают.Python: поведение сборщика мусора

Я провел последние два дня, исследуя это и узнав о сборке мусора 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), они будут удалены. И, как я уже упоминал, там также удаляется, когда происходит следующая автоматическая развертка gen 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, например?

ответ

2

Я думаю, что ваш анализ выглядит здоровым. Я не эксперт по gc, поэтому всякий раз, когда у меня возникает такая проблема, я просто добавляю звонок в gc.collect() в подходящее, не критичное для времени место и забываю об этом.

Я предлагаю вам позвонить gc.collect() на ваш взгляд (-и) и посмотреть, какое влияние он оказывает на ваше время отклика и использование вашей памяти.

Примечание также this question, который предполагает, что настройка DEBUG=True ест память, как будто она почти не продается по дате.

+0

+1 для упоминания установки DEBUG = False, поэтому Django не регистрирует все ваши SQL-запросы. – Kekoa

+0

Вау, какой ментальный образ Django есть много еды –

3

Означает ли это вышеизложенное, или мне нужно искать проблему в другом месте? Неужели это несчастный случай, что значимые данные хранятся так долго в этом конкретном случае использования?

Да, это имеет смысл. И да, есть другие вопросы, которые стоит рассмотреть. Django использует threading.local в качестве базы для DatabaseWrapper (и некоторые вкладчики используют его, чтобы сделать объект запроса доступным из мест, где он не передается явно). Эти глобальные объекты сохраняют запросы и могут поддерживать ссылки на объекты до тех пор, пока в потоке не будет обработано какое-либо другое представление.

Есть ли что-нибудь, что я могу сделать, чтобы избежать проблемы. Я уже вижу некоторый потенциал для оптимизации представления, но это похоже на решение с ограниченным охватом - хотя я не уверен, что я тоже был бы общим; как это целесообразно, например, для вызова gc.collect() или gc.set_threshold() вручную?

Общие рекомендации (возможно, вы это знаете, но в любом случае): избегайте циркулярных ссылок и глобалов (включая threading.local). Попытайтесь сломать циклы и очистить глобалы, когда дизайн django затрудняет их устранение. gc.get_referrers(obj) может помочь вам найти интересные места. Другой способ отключить сборщик мусора и вызвать его вручную после каждого запроса, когда это лучшее место для работы (это предотвратит переход объектов к следующему поколению).

Я не думаю, что существует способ сделать gc.get_objects() по генерации, т. Е. Только получить объекты из поколения 2, например?

К сожалению, это невозможно с интерфейсом gc. Но есть несколько способов пойти. Вы можете рассмотреть конец списка, возвращенный только gc.get_objects(), поскольку объекты в этом списке сортируются по генерации.Вы можете сравнить список с одним возвращенным из предыдущего вызова, сохраняя слабые ссылки на них (например, в WeakKeyDictionary) между вызовами. Вы можете переписать gc.get_objects() в свой собственный C-модуль (это просто, в основном, программа для копирования-вставки!), Поскольку они хранятся по поколениям или даже имеют доступ к внутренним структурам с ctypes (требуется довольно глубокое понимание ctypes).

+0

get_objects() сортируется, достаточно хорошо, спасибо за подсказку. – miracle2k

Смежные вопросы