2009-08-27 23 views
24

Tengo un pequeño script multiproceso ejecutando en django y con el tiempo comienza a usar más y más memoria. Dejándolo por un día completo come alrededor de 6GB de RAM y empiezo a intercambiar.Python: depuración de fugas de memoria

Siguiendo http://www.lshift.net/blog/2008/11/14/tracing-python-memory-leaks Veo esto como los tipos más comunes (con sólo 800M de memoria utilizada):

(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 

que no muestra nada raro. ¿Qué debo hacer ahora para ayudar a depurar los problemas de memoria?

Actualización: Probando algunas cosas que la gente está recomendando. Ejecuté el programa de la noche a la mañana y, cuando lo hago, utilizo un 50% * 8G == 4G de RAM.

(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 

Eso no se suma a 4G, ni tampoco me da ningún big data estructurado para solucionarlo. El Unicode es de un conjunto() de nodos "hechos", y la lista parece simplemente aleatoria weakref s.

No utilicé guppy ya que requería una extensión C y no tenía root, así que me iba a costar construir.

Ninguno de los objetos que estaba usando tiene un método __del__, y mirando a través de las bibliotecas, no parece que ni django ni python-mysqldb lo hagan. ¿Alguna otra idea?

+0

"running in Django"? ¿Quiere decir que está utilizando el servidor web de Django para hacer un procesamiento de fondo adicional que no sea del servicio web? ¿Has considerado dividir este material que no es web en un proceso separado? –

+2

Es un trabajo de cron que importa Django settgings.py y usa muchas de las funciones de ORM de Django. Por lo tanto, no es generado por un servidor web, pero aún usa muchas de las características (que podrían haber sido pertinentes) –

Respuesta

31

Ver http://opensourcehacker.com/2008/03/07/debugging-django-memory-leak-with-trackrefs-and-guppy/. Respuesta corta: si está ejecutando django pero no en un formato basado en solicitud web, debe ejecutar manualmente db.reset_queries() (y, por supuesto, DEBUG = False, como han mencionado otros). Django automáticamente hace reset_queries() después de una solicitud web, pero en su formato, eso nunca sucede.

+2

db.reset_queries() resolvió un problema para mí, muchas gracias. – pyeleven

0

Probar Guppy.

Básicamente, necesita más información o puede extraer algo. Guppy incluso proporciona una representación gráfica de los datos.

1

Creo que debería usar diferentes herramientas. Aparentemente, las estadísticas que obtienes solo se refieren a objetos GC (es decir, objetos que pueden participar en ciclos); más notablemente, carece de cuerdas.

Recomiendo usar Pympler; esto debería proporcionarle estadísticas más detalladas.

+0

arriba muestra mi aplicación usando 7% * 8GB = 560M. pympler.muppy.print_summary() muestra alrededor de 55M. ¿Donde esta el resto? –

6

¿Has probado gc.set_debug()?

Es necesario hacerse preguntas sencillas:

  • Estoy usando objetos con __del__ métodos? ¿Los necesito absoluta e inequívocamente?
  • ¿Puedo obtener ciclos de referencia en mi código? ¿No podemos romper estos círculos antes de deshacernos de los objetos?

Sede, el tema principal sería un ciclo de objetos que contienen __del__ métodos:

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

Realmente te animaría a los objetos de bandera/conceptos que se completa un ciclo en su aplicación y centrarse en su vida: cuando ya no los necesites, ¿tenemos algo que lo haga referencia?

Incluso para los ciclos sin __del__ métodos, que pueden tener un problema:

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

Si usted tiene que utilizar "visto" -como los diccionarios, o la historia, tenga cuidado de mantener sólo los datos real que necesita y sin referencias externas a esto.

Estoy un poco decepcionado ahora por set_debug, me gustaría que se pudiera configurar para generar datos en otro lugar que no sea stderr, pero con suerte that should change soon.

+0

gc.collect() devuelve todo como coleccionable, y en la segunda invocación devuelve 0. Eso significa que no tengo ningún ciclo ¿verdad? –

+0

@Paul: No, aún puede tener ciclos. Mire el último ejemplo que di: aquí, gc.collect() devuelve 0 y no se imprime nada. Si tiene ciclos de objetos que no tienen métodos __del__, gc se mantendrá en silencio. – NicDumZ

1

¿Utiliza alguna extensión? Son un lugar maravilloso para las pérdidas de memoria, y no serán rastreados por las herramientas de Python.

+0

Sin extensiones, pero es un buen lugar para que otros tropiecen aquí para mirar. –

+0

Si usa Django ORM, usa el módulo de extensión - controlador de base de datos DB-API. ¿Es esto MySQLdb? La versión actual tiene una fuga conocida de memoria del cursor cuando se establece la conexión con use_unicode = True (que es el caso de Django> = 1.0). – zgoda

+0

¡sí, tienes razón en el dinero! Estoy usando todos esos. ¿Alguna solución conocida? –

19

¿DEPARTÓ = Falso en settings.py?

Si no, Django estará encantado de almacenar todas las consultas SQL que hagas y que sumen.

+2

wow, sabía que escribir las palabras django allí ayudaría. Sí, mi script no usaba mi configuración de producción.py *desconcertado*. Veamos si aclara el problema de memoria. –

+0

Esto es todo! El DEPURADOR establecido en True realmente consume mucha memoria cuando se selecciona desde una base de datos grande. –