Почему память не высвобождается в систему после больших запросов (или серии запросов) в django? - PullRequest
23 голосов
/ 31 марта 2011

Во-первых, DEBUG = False в settings.py, так что нет, connections['default'].queries не увеличивается и не увеличивается, пока не израсходует всю память.

Давайте начнем с того, что я загрузил таблицу User из django.contrib.auth.models.User для 10000 пользователей (каждый с именем 'test #', где # - это число от 1 до 10000).

Вот вид:

from django.contrib.auth.models import User
from django.http import HttpResponse

import time

def leak(request):
    print "loading users"

    users = []
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())

    print "sleeping"
    time.sleep(10)

    return HttpResponse('')

Я прикрепил приведенное выше представление к URL-адресу /leak/ и запустил сервер разработки (с DEBUG = False, и я проверил, и он не имеет ничего для работы с сервером разработки против другие случаи).

После запуска:

% curl http://localhost:8000/leak/

Объем памяти процесса runserver увеличивается примерно до размера, видимого из вывода ps aux ниже, а затем остается на этом уровне.

USER       PID %CPU %MEM    VSZ    RSS TTY      STAT START   TIME COMMAND
dlamotte 25694 11.5 34.8 861384 705668 pts/3    Sl+  19:11   2:52 /home/dlamotte/tmp/django-mem-leak/env/bin/python ./manage.py runserver

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

После этого я наивно попытался выяснить, выпустит ли python большие куски памяти, которые он выделил. Поэтому я пытаюсь сделать следующее из сессии Python:

>>> a = ''
>>> a += 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' * 10000000
>>> del a

Память выделяется в строке a += ..., как и ожидалось, но когда происходит del a, память освобождается. Почему поведение отличается для наборов запросов django? Это то, что Django собирается сделать? Есть ли способ изменить это поведение?

Я буквально потратил 2 дня на отладку этого поведения, не зная, куда идти дальше (я научился использовать гуппи и objgraph, которые, кажется, не указывают ни на что интересное, что я могу выяснить).

ОБНОВЛЕНИЕ: Это может быть просто управление памятью Python на работе и не иметь никакого отношения к Django (рекомендуется в списке рассылки django-users), но я хотел бы получить подтверждение, каким-то образом повторив это в python за пределами Джанго.

ОБНОВЛЕНИЕ: Использование Python версии 2.6.5

Ответы [ 2 ]

24 голосов
/ 31 марта 2011

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

Начиная с Python 2.5, распределение памяти CPython отслеживает использование внутренней памяти распределителем небольших объектов и пытается вернуть полностью свободные области вбазовая ОС.Это работает большую часть времени, но тот факт, что объекты не могут перемещаться в памяти, означает, что фрагментация может быть серьезной проблемой.

Попробуйте следующий эксперимент (я использовал 3.2, но 2.5+ должно быть похожеесли вы используете xrange):

# Create the big lists in advance to avoid skewing the memory counts
seq1 = [None] * 10**6 # Big list of references to None
seq2 = seq1[::10]

# Create and reference a lot of smaller lists
seq1[:] = [[] for x in range(10**6)] # References all the new lists
seq2[:] = seq1[::10] # Grab a second reference to 10% of the new lists

# Memory fragmentation in action
seq1[:] = [None] * 10**6 # 90% of the lists are no longer referenced here
seq2[:] = seq1[::10] # But memory freed only after last 10% are dropped

Обратите внимание, что даже если вы отбросите ссылки на seq1 и seq2, приведенная выше последовательность, скорее всего, оставит ваш процесс Python с большим объемом дополнительной памяти.

Когда люди говорят о PyPy, используя меньше памяти, чем CPython, это основная часть того, о чем они говорят.Поскольку PyPy не использует прямые ссылки на указатели под капотом, он может использовать сжатие GC, что позволяет избежать значительной части проблемы фрагментации и более надежно возвращать память в ОС.

5 голосов
/ 31 марта 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...