Python: отладка утечки памяти - PullRequest
24 голосов
/ 27 августа 2009

У меня есть небольшой многопоточный скрипт, работающий в django, и со временем он начинает использовать все больше и больше памяти. Если оставить его на целый день, то съедает около 6 ГБ ОЗУ, и я начинаю менять местами.

После http://www.lshift.net/blog/2008/11/14/tracing-python-memory-leaks Я считаю это наиболее распространенными типами (при использовании только 800 МБ памяти):

(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 оперативной памяти.

(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 и не дает мне больших данных, структурированных для исправления. Юникод взят из набора () "готовых" узлов, а список выглядит просто случайным weakref с.

Я не использовал гуппи, так как для этого требовалось расширение C, и у меня не было root, поэтому собирать его было бы сложно.

Ни у одного из объектов, которые я использовал, не было метода __del__, и, просматривая библиотеки, он не похож ни на django, ни на python-mysqldb. Есть еще идеи?

Ответы [ 7 ]

31 голосов
/ 10 февраля 2011

См. http://opensourcehacker.com/2008/03/07/debugging-django-memory-leak-with-trackrefs-and-guppy/. Краткий ответ: если вы используете django, но не в формате на основе веб-запросов, вам нужно вручную запустить db.reset_queries() (и, конечно, как указано другими, DEBUG = False). Django автоматически делает reset_queries() после веб-запроса, но в вашем формате этого никогда не происходит.

19 голосов
/ 27 августа 2009

DEBUG = False в settings.py?

Если нет, Django с радостью сохранит все ваши запросы SQL, которые суммируются.

6 голосов
/ 27 августа 2009

Вы пробовали gc.set_debug () ?

Вам нужно задать себе простые вопросы:

  • Использую ли я объекты с __del__ методами? Нужны ли они мне абсолютно однозначно?
  • Могу ли я получить справочные циклы в моем коде? Разве мы не можем разбить эти круги, прежде чем избавиться от объектов?

Видите ли, основной проблемой будет цикл объектов, содержащих __del__ методы:

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__ методов у нас может возникнуть проблема:

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()

Если вам необходимо использовать словари или историю, похожие на «видимые», будьте осторожны, сохраняя только те данные, которые вам нужны, и не обращаясь к ним со стороны.

Я немного разочарован сейчас set_debug, я бы хотел, чтобы его можно было настроить для вывода данных куда-то, кроме stderr, но, надеюсь, , который скоро должен измениться .

5 голосов
/ 28 августа 2009

См. этот превосходный пост в блоге от Неда Батчелдера о том, как они обнаружили настоящую утечку памяти в Tabblo от HP. Классика, которую стоит прочитать.

1 голос
/ 27 августа 2009

Вы используете какое-либо расширение? Они являются прекрасным местом для утечек памяти и не будут отслеживаться инструментами Python.

1 голос
/ 27 августа 2009

Я думаю, вы должны использовать разные инструменты. По-видимому, полученная вами статистика касается только объектов GC (т.е. объектов, которые могут участвовать в циклах); в частности, в нем отсутствуют строки.

Рекомендую использовать Pympler ; это должно предоставить вам более подробную статистику.

0 голосов
/ 27 августа 2009

Попробуйте Гуппи .

Как правило, вам нужна дополнительная информация или возможность извлечь ее. Гуппи даже обеспечивает графическое представление данных.

...