¿La recolección de basura de Python puede ser tan lenta?

Tengo un problema con mi aplicación de python y creo que está relacionado con la recolección de basura de python, incluso si no estoy seguro …

El problema es que mi aplicación tarda mucho tiempo en salir y cambiar de una función a la siguiente.

En mi aplicación, manejo diccionarios muy grandes, que contienen miles de objetos grandes que se crean instancias de clases de C ++ envueltas.

Puse algunas salidas de marca de tiempo en mi progtwig, y ​​vi que al final de cada función, cuando los objetos creados dentro de la función deberían quedar fuera del scope, el intérprete pasa mucho tiempo antes de llamar a la siguiente función. Y observo el mismo problema al final de la aplicación, cuando el progtwig debería salir: ¡se pasa mucho tiempo (~ ¡horas!) Entre la última marca de tiempo en la pantalla y la aparición del nuevo indicador.

El uso de la memoria es estable, por lo que realmente no tengo pérdidas de memoria.

¿Alguna sugerencia?

¿Puede ser la recolección de basura de miles de objetos C ++ grandes que se ralentizan?

¿Hay un método para acelerar eso?

ACTUALIZAR:

Muchas gracias por todas sus respuestas, me dio muchos consejos para depurar mi código 🙂

Uso Python 2.6.5 en Scientific Linux 5, una distribución personalizada basada en Red Hat Enterprise 5. Y en realidad no uso SWIG para obtener enlaces Python para nuestro código C ++, sino el marco Reflex / PyROOT. Lo sé, no es muy conocido fuera de la física de partículas (pero sigue siendo de código abierto y está disponible gratuitamente) y tengo que usarlo porque es el valor predeterminado para nuestro marco principal.

Y en este contexto, el comando DEL del lado de Python no funciona, ya lo había probado. DEL solo elimina la variable python vinculada al objeto C ++, no el objeto en memoria, que aún es propiedad del lado C ++ …

… Lo sé, no es estándar, supongo, y un poco complicado, lo siento 😛

Pero siguiendo tus sugerencias, perfilaré mi código y te responderé con más detalles, como sugeriste.

ACTUALIZACIÓN ADICIONAL:

Ok, siguiendo tus sugerencias, instrumenté mi código con cProfile , y descubrí que en realidad la función gc.collect() es la función ¡¡¡aprovechando al máximo el tiempo de ejecución !!

Aquí la salida de cProfile + pstats print_stats ():


     >>> p.sort_stats ("time"). print_stats (20)
 Mié 20 de octubre 17:46:02 2010 mainProgram.profile

          547303 llamadas de función (542629 llamadas primitivas) en 548.060 segundos de CPU

    Ordenado por: tiempo interno
    Lista reducida de 727 a 20 por restricción. 

    ncalls tottime percall cumtime percall filename: lineno (función)
         4 345.701 86.425 345.704 86.426 {gc.collect}
         1 167.115 167.115 200.946 200.946 PlotD3PD_v3.2.py:2041(PlotSamplesBranches)
        28 12.817 0.458 13.345 0.477 PlotROOTUtils.py:205(SaveItems)
      9900 10.425 0.001 10.426 0.001 PlotD3PD_v3.2.py:1973(HistoStyle)
      6622 5.188 0.001 5.278 0.001 PlotROOTUtils.py:403(__init__)
        57 0.625 0.011 0.625 0.011 {carga del método incorporado}
       103 0.625 0.006 0.792 0.008 dbutils.py:41(DeadlockWrap)
        14 0.475 0.034 0.475 0.034 {método 'dump' of 'cPickle.Pickler' objetos}
      6622 0.453 0.000 5.908 0.001 PlotROOTUtils.py:421(CreateCanvas)
     26455 0.434 0.000 0.508 0.000 /opt/root/lib/ROOT.py:215(__getattr__)
 [...]

 >>> p.sort_stats ("acumulativo"). print_stats (20)
 Mié 20 de octubre 17:46:02 2010 mainProgram.profile

          547303 llamadas de función (542629 llamadas primitivas) en 548.060 segundos de CPU

    Ordenado por: tiempo acumulado
    Lista reducida de 727 a 20 por restricción. 

    ncalls tottime percall cumtime percall filename: lineno (función)
         1 0.001 0.001 548.068 548.068 PlotD3PD_v3.2.py:2492(main)
         4 0.000 0.000 346.756 86.689 /usr/lib//lib/python2.5/site-packages/guppy/heapy/Use.py:171(heap)
         4 0.005 0.001 346.752 86.688 /usr/lib//lib/python2.5/site-packages/guppy/heapy/View.py:344(heap)
         1 0.002 0.002 346.147 346.147 PlotD3PD_v3.2.py:2537(LogAndFinalize)
         4 345.701 86.425 345.704 86.426 {gc.collect}
         1 167.115 167.115 200.946 200.946 PlotD3PD_v3.2.py:2041(PlotBranches)
        28 12.817 0.458 13.345 0.477 PlotROOTUtils.py:205(SaveItems)
      9900 10.425 0.001 10.426 0.001 PlotD3PD_v3.2.py:1973(HistoStyle)
     13202 0.336 0.000 6.818 0.001 PlotROOTUtils.py:431(PlottingCanvases)
      6622 0.453 0.000 5.908 0.001 / root / svn_co / rbianchi / SoftwareDevelopment

 [...]

 >>>

Por lo tanto, en ambas salidas, ordenadas por “tiempo” y por tiempo “acumulativo” respectivamente, gc.collect() es la función que consume la mayor parte del tiempo de ejecución de mi progtwig. :-PAG

Y esta es la salida del generador de perfiles de memoria, justo antes de devolver el progtwig main() .

 uso de memoria antes de la devolución:
 Partición de un conjunto de 65901 objetos.  Tamaño total = 4765572 bytes.
  Índice Cantidad% Tamaño% Cumulativo% Clase (clase / dictado de clase)
      0 25437 39 1452444 30 1452444 30 str
      1 6622 10 900592 19 2353036 49 dict de PlotROOTUtils.Canvas
      2 109 0 567016 12 2920052 61 dict del módulo
      3 7312 11 280644 6 3200696 67 tupla
      4 6622 10 238392 5 3439088 72 0xa4ab74c
      5 6622 10 185416 4 3624504 76 PlotROOTUtils.Canvas
      6 2024 3 137632 3 3762136 79 tipos.CodeType
      7 263 0 129080 3 3891216 82 dict (sin propietario)
      8 254 0 119024 2 4010240 84 dict de tipo
      9 254 0 109728 2 4119968 86 tipo
   Índice Cantidad% Tamaño% Cumulativo% Clase (clase / dictado de clase)
     10 1917 3 107352 2 4264012 88 función
     11 3647 5 102116 2 4366128 90 ROOT.MethodProxy
     12 148 0 80800 2 4446928 92 dict de clase
     13 1109 2 39924 1 4486852 93 __builtin __. Wrapper_descriptor
     14 239 0 23136 0 4509988 93 lista
     15 87 0 22968 0 4532956 94 dict de guppy.etc.Glue.Interface
     16 644 1 20608 0 4553564 94 tipos.BuiltinFunctionType
     17 495 1 19800 0 4573364 94 __builtin __. Weakref
     18 23 0 11960 0 4585324 95 dict de guppy.etc.Glue.Share
     19 367 1 11744 0 4597068 95 __builtin __. Method_descriptor

¿Alguna idea de por qué, o cómo optimizar la recolección de basura?

¿Hay alguna verificación más detallada que pueda hacer?

Este es un problema conocido del recolector de basura en Python 2.6 que causa un tiempo cuadrático para la recolección de basura cuando se asignan muchos objetos sin desasignar ninguno de ellos, es decir. Población de lista grande.
Hay dos soluciones simples:

  1. o bien deshabilite la recolección de basura antes de llenar listas grandes y habilítela después

     l = [] gc.disable() for x in xrange(10**6): l.append(x) gc.enable() 
  2. o actualizar a Python 2.7, donde el problema ha sido resuelto

Prefiero la segunda solución, pero no siempre es una opción;)

Sí, podría ser una recolección de basura, pero también podría ser una sincronización con el código C ++, o algo completamente diferente (difícil de decir sin código).

De todos modos, debería echar un vistazo a SIG para el desarrollo de la integración de Python / C ++ para encontrar problemas y cómo acelerarlos.

Si su problema realmente es la recolección de basura, intente liberar sus objetos explícitamente cuando haya terminado con ellos usando del() .

En general, esto no suena como un problema de recolección de basura, a menos que estemos hablando de terabytes de memoria.

Estoy de acuerdo con S.Lott … haga un perfil de su aplicación, luego traiga fragmentos de código y los resultados de eso y podemos ser mucho más útiles.