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

dla*_*tte 24 python django memory-leaks

首先,DEBUG = False在settings.py中,所以不,connections['default'].queries不会增长和增长,直到它耗尽所有内存.

让我们从我用10000个用户加载User表的事实开始django.contrib.auth.models.User(每个用户名为'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('')
Run Code Online (Sandbox Code Playgroud)

我已将上面的视图附加到/leak/url并启动开发服务器(使用DEBUG = False,我已经测试过,它与运行开发服务器与其他实例无关).

运行后:

% curl http://localhost:8000/leak/
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

然后运行上面的curl命令似乎没有增加实例的内存使用量(我期望从真正的内存泄漏?),它必须重新使用内存?但是,我觉得这里有一些错误,内存没有被释放到系统中(但是,据我所知,python不会释放内存可能是更好的性能).

在此之后,我天真地试图看看python是否会释放它分配的大块内存.所以我从python会话中尝试以下内容:

>>> a = ''
>>> a += 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' * 10000000
>>> del a
Run Code Online (Sandbox Code Playgroud)

内存a += ...按预期在行上分配,但是当del a发生时,内存被释放.为什么django查询集的行为不同?这是django打算做的事情吗?有没有办法改变这种行为?

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

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

更新:使用python版本2.6.5

nco*_*lan 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
Run Code Online (Sandbox Code Playgroud)

请注意,即使你删除掉引用seq1seq2,上面的顺序可能会离开你的Python进程拿着很多额外的内存.

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


Kyl*_*tan 5

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

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

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