关于django:Python:内存泄漏调试

Python: Memory leak debugging

我有一个小的多线程脚本在Django中运行,随着时间的推移,它开始使用越来越多的内存。把它放一整天就可以吃掉6GB的内存,然后我开始交换。

根据http://www.lshift.net/blog/2008/11/14/tracing-python-memory-leaks,我认为这是最常见的类型(仅使用800m内存):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(Pdb)  objgraph.show_most_common_types(limit=20)
dict                       43065
tuple                      28274
function                   7335
list                       6157
NavigableString            3479
instance                   2454
cell                       1256
weakref                    974
wrapper_descriptor         836
builtin_function_or_method 766
type                       742
getset_descriptor          562
module                     423
method_descriptor          373
classobj                   256
instancemethod             255
member_descriptor          218
property                   185
Comment                    183
__proxy__                  155

没有什么奇怪的。我现在应该做什么来帮助调试内存问题?

更新:尝试一些人们推荐的东西。我用了一整夜的时间运行程序,当我工作时,使用了50%*8g==4G的RAM。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(Pdb) from pympler import muppy
(Pdb) muppy.print_summary()
                                     types |   # objects |   total size
========================================== | =========== | ============
                                   unicode |      210997 |     97.64 MB
                                      list |        1547 |     88.29 MB
                                      dict |       41630 |     13.21 MB
                                       set |          50 |      8.02 MB
                                       str |      109360 |      7.11 MB
                                     tuple |       27898 |      2.29 MB
                                      code |        6907 |      1.16 MB
                                      type |         760 |    653.12 KB
                                   weakref |        1014 |     87.14 KB
                                       int |        3552 |     83.25 KB
                    function (__wrapper__) |         702 |     82.27 KB
                        wrapper_descriptor |         998 |     77.97 KB
                                      cell |        1357 |     74.21 KB
  <class 'pympler.asizeof.asizeof._Claskey |        1113 |     69.56 KB
                       function (__init__) |         574 |     67.27 KB

这不等于4G,也不真正给我任何大数据结构去修复。unicode来自一组"done"节点,列表看起来就像是随机的weakrefs。

我没有使用Guppy,因为它需要C扩展,而且我没有根,所以构建起来会很痛苦。

没有一个objecti使用的是have-__del__方法,并且通过库查看,它看起来既不像django,也不像python mysqldb。还有其他想法吗?


请参阅http://opensourcehacker.com/2008/03/07/debugging-django-memory-leak-with-trackrefs-and-guppy/。简短回答:如果您运行的是Django,但不是基于Web请求的格式,则需要手动运行db.reset_queries()(当然,如其他人提到的,debug=false)。Django在Web请求后自动执行reset_queries(),但以您的格式,这种情况永远不会发生。


在settings.py中debug=false吗?

如果不是这样,Django会很高兴地存储您所做的所有SQL查询,这些查询加起来就是。


您尝试过gc.set_debug()吗?

你需要问自己一些简单的问题:

  • 我是否将对象与__del__方法一起使用?我真的,明确地,需要他们吗?
  • 我能在代码中得到参考周期吗?我们不能在摆脱这些物体之前打破这些圆圈吗?

看,主要问题是包含__del__方法的对象的循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import gc

class A(object):
    def __del__(self):
        print 'a deleted'
        if hasattr(self, 'b'):
            delattr(self, 'b')

class B(object):
    def __init__(self, a):
        self.a = a
    def __del__(self):
        print 'b deleted'
        del self.a


def createcycle():
    a = A()
    b = B(a)
    a.b = b
    return a, b

gc.set_debug(gc.DEBUG_LEAK)

a, b = createcycle()

# remove references
del a, b

# prints:
## gc: uncollectable <A 0x...>
## gc: uncollectable <B 0x...>
## gc: uncollectable <dict 0x...>
## gc: uncollectable <dict 0x...>
gc.collect()

# to solve this we break explicitely the cycles:
a, b = createcycle()
del a.b

del a, b

# objects are removed correctly:
## a deleted
## b deleted
gc.collect()

我真的鼓励您标记正在应用程序中循环的对象/概念,并关注它们的生命周期:当您不再需要它们时,我们是否有任何引用它的内容?

即使对于没有__del__方法的循环,我们也有一个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import gc

# class without destructor
class A(object): pass

def createcycle():
    # a -> b -> c
    # ^         |
    # ^<--<--<--|
    a = A()
    b = A()
    a.next = b
    c = A()
    b.next = c
    c.next = a
    return a, b, b

gc.set_debug(gc.DEBUG_LEAK)

a, b, c = createcycle()
# since we have no __del__ methods, gc is able to collect the cycle:

del a, b, c
# no panic message, everything is collectable:
##gc: collectable <A 0x...>
##gc: collectable <A 0x...>
##gc: collectable <dict 0x...>
##gc: collectable <A 0x...>
##gc: collectable <dict 0x...>
##gc: collectable <dict 0x...>
gc.collect()

a, b, c = createcycle()

# but as long as we keep an exterior ref to the cycle...:
seen = dict()
seen[a] = True

# delete the cycle
del a, b, c
# nothing is collected
gc.collect()

如果您必须使用"seen"(如字典或历史),请注意只保留所需的实际数据,而不保留对它的外部引用。

我现在对set_debug有点失望,我希望它可以配置为输出数据,而不是输出到stderr,但希望这很快就会改变。


看看这篇来自NedBatchelder的优秀博客文章,了解他们是如何追踪到惠普Tabblo中的真正内存泄漏的。经典而值得一读。


你用分机吗?它们是内存泄漏的好地方,不会被Python工具跟踪。


我认为你应该使用不同的工具。显然,您得到的统计信息只是关于GC对象(即可能参与循环的对象);最明显的是,它缺少字符串。

我建议使用pympler;这将为您提供更详细的统计信息。


试试古皮。

基本上,你需要更多的信息或者能够提取一些。Guppy甚至提供了数据的图形化表示。