2009-09-16 18 views
112

Tengo un script de larga duración que, si lo dejo funcionar lo suficiente, consumirá toda la memoria de mi sistema.Fugas de memoria de Python

Sin entrar en detalles sobre el guión, tengo dos preguntas:

  1. ¿Hay alguna "mejores prácticas" a seguir, lo que ayudará a prevenir las fugas que se produzcan?
  2. ¿Qué técnicas existen para depurar fugas de memoria en Python?
+3

He encontrado [esta receta] (http://code.activestate.com/recipes/65333/ útil). –

+0

Parece imprimir demasiados datos para ser útil – Casebash

+1

@Casebash: Si esa función imprime algo, lo está haciendo seriamente mal. Enumera objetos con el método '__del__' al que ya no se hace referencia, excepto por su ciclo. El ciclo no se puede romper debido a problemas con '__del__'. ¡Arreglalo! –

Respuesta

4

No estoy seguro acerca de las "Mejores prácticas" para las fugas de memoria en python, pero Python debe borrar su propia memoria por su recolector de basura. Entonces, principalmente comenzaría por buscar una lista circular de algunos cortos, ya que no serán recogidos por el recolector de basura.

+3

o referencias a objetos que se mantienen para siempre, etc. –

+0

¿Pueden darnos ejemplos de listas circulares y objetos que se guardan para siempre? – Daniel

8

Especialmente debe consultar sus datos globales o estáticos (datos de larga duración).

Cuando estos datos crecen sin restricciones, también puede obtener problemas en Python.

El recolector de basura solo puede recopilar datos, que ya no se mencionan. Pero sus datos estáticos pueden conectar elementos de datos que deberían liberarse.

Otro problema pueden ser los ciclos de memoria, pero al menos en teoría el recolector de basura debe encontrar y eliminar los ciclos, al menos mientras no se enganchen con algunos datos largos de vida.

¿Qué tipos de datos de larga duración son especialmente problemáticos? Eche un vistazo a las listas y diccionarios: pueden crecer sin límite. En los diccionarios, puede que incluso no vea el problema ya que cuando acceda a los dictados, la cantidad de claves en el diccionario podría no ser de gran visibilidad ...

3

Esto no es un consejo exhaustivo. Pero lo más importante a tener en cuenta al escribir con la idea de evitar fugas de memoria futuras (bucles) es asegurarse de que cualquier cosa que acepte una referencia a una devolución de llamada, almacene esa devolución de llamada como una referencia débil.

13

Déjenme recomendar mem_top herramienta,
que me ayudó a resolver un problema similar.

Muestra instantáneamente los principales sospechosos de fugas de memoria en un programa de Python.

+0

eso es cierto ... pero da muy poco en cuanto a la explicación de uso/resultados –

+0

@me_, esta herramienta tiene documentadas las secciones "Uso" y "Explicar el resultado". ¿Debo agregar una explicación como "refs es el recuento de referencias del objeto, types es el recuento de los objetos de este tipo, bytes es el tamaño del objeto" - ¿no sería demasiado obvio para documentar esto? –

+0

los documentos de uso de la herramienta dan una sola línea que dice "de vez en cuando: logging.debug (mem_top())", mientras que su explicación de resultados es la experiencia de seguimiento del error de la vida real sin contexto ... eso no es una especificación técnica que le dice a un desarrollador exactamente lo que están mirando ... No estoy llamando su respuesta ... muestra a sospechosos de alto nivel según lo facturado ... no proporciona documentación adecuada para comprender completamente el resultado del uso ... para Por ejemplo, en la salida "Explicando resultados", ¿por qué es obviamente "GearmanJobRequest" un problema? ninguna explicación de por qué ... –

51

Probé la mayoría de las opciones mencionadas anteriormente, pero encontró este paquete pequeño e intuitiva para ser el mejor: pympler

Es bastante recta hacia adelante para rastrear objetos que no eran recolección de basura, compruebe este pequeño ejemplo:

instalar el paquete a través pip install pympler

from pympler.tracker import SummaryTracker 
tracker = SummaryTracker() 

# ... some code you want to investigate ... 

tracker.print_diff() 

la salida muestra todos los objetos que se han añadido, además de la memoria que consumían.

Resultado de muestra:

        types | # objects | total size 
====================================== | =========== | ============ 
            list |  1095 | 160.78 KB 
            str |  1093 |  66.33 KB 
            int |   120 |  2.81 KB 
            dict |   3 |  840 B 
     frame (codename: create_summary) |   1 |  560 B 
      frame (codename: print_diff) |   1 |  480 B 

Este paquete proporciona un número de más funciones. Compruebe pympler's documentation, en particular, la sección Identifying memory leaks.

+1

Cosa graciosa ... mi fuga de memoria en realidad desapareció cuando comencé a usar el linceador para intentar rastrearlo. Probablemente algún problema de recolección de basura ... – sebpiq

+1

Vale la pena señalar que 'bombeador' puede ser ** LENTO **. Si está haciendo algo en tiempo semi real, puede paralizar por completo el rendimiento de la aplicación. –

2

En cuanto a las mejores prácticas, mantener un ojo para las funciones recursivas. En mi caso, tuve problemas con la recursión (donde no era necesario). Un ejemplo simplificado de lo que estaba haciendo:

def my_function(): 
    # lots of memory intensive operations 
    # like operating on images or huge dictionaries and lists 
    ..... 
    my_flag = True 
    if my_flag: # restart the function if a certain flag is true 
     my_function() 

def main(): 
    my_function() 

operando de esta manera recursiva no activará la recolección de basura y limpiar los restos de la función, por lo que cada vez a través de uso de la memoria está creciendo y creciendo.

Mi solución fue sacar la llamada recursiva de my_function() y hacer que main() manejara cuándo volver a llamar. de esta manera, la función finaliza naturalmente y se limpia después de sí misma.

def my_function(): 
    # lots of memory intensive operations 
    # like operating on images or huge dictionaries and lists 
    ..... 
    my_flag = True 
    ..... 
    return my_flag 

def main(): 
    result = my_function() 
    if result: 
     my_function() 
+4

Usar recursion de esta manera también se romperá si alcanza el límite de profundidad de recursión porque Python no optimiza las llamadas finales. Por defecto, esto es 1000 llamadas recursivas. –

3

Para detectar y localizar fugas de memoria en procesos de larga ejecución, p. en entornos de producción, ahora puede usar stackimpact. Utiliza tracemalloc debajo. Más información en this post.

enter image description here

4

Tracemalloc module fue integrado como un módulo integrado a partir de Python 3.4, y appearently, también está disponible para las versiones anteriores de Python como a third-party library (no han probado sin embargo).

Este módulo es capaz de dar salida a los archivos y las líneas que asignan la mayor cantidad de memoria precisas. En mi humilde opinión, esta información es infinitamente más valiosa que el número de instancias asignadas para cada tipo (que termina siendo muchas tuplas el 99% del tiempo, lo cual es una pista, pero apenas ayuda en la mayoría de los casos).

le recomiendo que utilice tracemalloc en combinación con pyrasite. 9 de cada 10 veces, ejecutar top 10 snippet en un pyrasite-shell le dará suficiente información y sugerencias para arreglar la fuga en 10 minutos. Sin embargo, si aún no puede encontrar la causa de la fuga, pyrasite-shell en combinación con las otras herramientas mencionadas en este hilo probablemente le dará más sugerencias. También debe echar un vistazo a todos los ayudantes adicionales proporcionados por pyrasite (como el visor de memoria).