为什么在django中进行大量查询(或一系列查询)后内存不会释放到系统?

时间:2011-03-31 00:51:02

标签: python django memory-leaks

首先,在settings.py中DEBUG = False,所以不,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('')

我已将上面的视图附加到/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打算做的事情吗?有没有办法改变这种行为?

我真的花了两天时间调试这个行为,不知道下一步该去哪里(我学会了使用guppy AND objgraph,似乎没有指出任何有趣的东西我可以弄清楚。)

更新:这可能只是工作中的python内存管理而与Django无关(在django-users邮件列表上建议),但我想通过某种方式在python中复制这个来确认在Django之外。

更新:使用python版本2.6.5

2 个答案:

答案 0 :(得分:24)

我决定将我的意见转移到一个让事情变得清晰的答案中。

从Python 2.5开始,CPython内存分配跟踪小对象分配器的内部内存使用情况,并尝试将完全免费的竞技场返回给底层操作系统。这在大多数情况下都有效,但是对象无法在内存中移动这一事实意味着碎片可能是一个严重的问题。

尝试以下实验(我使用3.2,但如果使用xrange,则2.5+应该相似):

# 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

注意,即使删除对seq1seq2的引用,上述序列也可能会使Python进程占用大量额外内存。

当人们使用比CPython更少的内存来谈论PyPy时,这是他们所谈论的内容的主要部分。因为PyPy不使用直接指针引用,所以它能够使用压缩GC,从而避免了大量的碎片问题,并且更可靠地将内存返回到操作系统。

答案 1 :(得分:5)

许多应用程序,语言运行时,甚至一些系统内存分配器都会尽可能长时间地保留已释放的内存,以便重新使用它,纯粹是出于性能目的。在像Django这样的复杂系统中,它可以是任何数量的扩展,可能是用C实现的,它们表现出这种行为,或者它可能是带有某种内存池或延迟垃圾收集的Python。

它甚至可以是执行此操作的基础malloc实现,或者即使进程未明确使用它,您的操作系统仍会为您的进程分配一定量的内存空间。不要引用我的话 - 自从我研究这些事情以来已经有一段时间了。

总的来说,如果在初始alloc和dealloc之后重复分配过程并没有使用的内存量增加一倍,那么你所看到的不是内存泄漏而是内存池。如果你有很多进程争夺该机器上有限的内存,那么它可能只是一个问题。