Python – Trabajando alrededor de las memory leaks

Tengo un progtwig Python que ejecuta una serie de experimentos, sin datos destinados a ser almacenados de una prueba a otra. Mi código contiene una pérdida de memoria que no puedo encontrar por completo (he visto los otros subprocesos de las pérdidas de memoria). Debido a limitaciones de tiempo, tuve que renunciar a encontrar la fuga, pero si pudiera aislar cada experimento, el progtwig probablemente duraría lo suficiente para producir los resultados que necesito.

  • ¿Ejecutar cada prueba en un hilo separado ayuda?
  • ¿Existen otros métodos para aislar los efectos de una fuga?

Detalle de la situación específica.

  • Mi código tiene dos partes: un corredor de experimento y el código de experimento real.
  • Aunque no se comparten globales entre el código para ejecutar todos los experimentos y el código utilizado por cada experimento, algunas clases / funciones se comparten necesariamente.
  • El corredor de experimentación no es solo un bucle for simple que se puede poner fácilmente en un script de shell. Primero decide las pruebas que deben ejecutarse dados los parámetros de configuración, luego ejecuta las pruebas y luego genera los datos de una manera particular.
  • Intenté llamar manualmente al recolector de basura en caso de que el problema fuera simplemente que no se estaba ejecutando la recolección de basura, pero esto no funcionó

Actualizar

La respuesta de Gnibbler realmente me ha permitido descubrir que mis objetos de cálculo de cercanía que almacenan todos los datos utilizados durante cada cálculo no se eliminan. Luego utilicé eso para borrar manualmente algunos enlaces que parecen haber solucionado los problemas de memoria.

    Puedes usar algo como esto para ayudar a localizar las memory leaks

    >>> from collections import defaultdict >>> from gc import get_objects >>> before = defaultdict(int) >>> after = defaultdict(int) >>> for i in get_objects(): ... before[type(i)] += 1 ... 

    Ahora supongamos que las pruebas pierden algo de memoria

     >>> leaked_things = [[x] for x in range(10)] >>> for i in get_objects(): ... after[type(i)] += 1 ... >>> print [(k, after[k] - before[k]) for k in after if after[k] - before[k]] [(, 11)] 

    11 porque hemos filtrado una lista que contiene 10 listas más

    Los hilos no ayudarían. Si debe renunciar a encontrar la fuga, entonces la única solución para contener su efecto es ejecutar un nuevo proceso de vez en cuando (por ejemplo, cuando una prueba ha dejado el consumo general de memoria demasiado alto para su gusto, puede determinar el tamaño de la máquina virtual). fácilmente leyendo /proc/self/status en Linux, y otros enfoques similares en otros sistemas operativos).

    Asegúrese de que la secuencia de comandos general tome un parámetro opcional para decirle desde qué número de prueba (u otra identificación de prueba) debe comenzar, de modo que cuando una instancia de la secuencia de comandos decide que está ocupando demasiada memoria, puede indicar a su sucesor desde dónde reiniciar .

    O, más sólidamente, asegúrese de que a medida que se completa cada prueba, su identificación se adjunte a un archivo con un nombre conocido. Cuando se inicia el progtwig, comienza por leer ese archivo y, por lo tanto, sabe qué pruebas ya se han ejecutado. Esta architecture es más sólida porque también cubre el caso en el que el progtwig se bloquea durante una prueba; por supuesto, para automatizar completamente la recuperación de dichos lockings, querrá que un progtwig y proceso de vigilancia independiente se encargue de iniciar una nueva instancia del progtwig de prueba cuando determine que el anterior se bloqueó (podría usar un subprocess para el propósito – también necesita una forma de saber cuándo finaliza la secuencia, por ejemplo, una salida normal del progtwig de prueba podría significar que, si bien cualquier falla o salida con un estado! = 0 significa que es necesario iniciar una nueva instancia nueva).

    Si estas architectures resultan atractivas, pero necesita más ayuda para implementarlas, solo comente esta respuesta y estaré encantado de proporcionarle un código de ejemplo. No quiero hacerlo “de forma preventiva” en caso de que haya problemas aún no expresados Eso hace que las architectures sean inadecuadas para ti. (También podría ayudar saber qué plataformas necesita para ejecutar).

    Tuve el mismo problema con una biblioteca C de terceros que tenía fugas. La solución de trabajo más limpia en la que podía pensar era en bifurcar y esperar. La ventaja de esto es que ni siquiera tiene que crear un proceso separado después de cada ejecución. Puede definir el tamaño de su lote.

    Aquí hay una solución general (si alguna vez encuentra la fuga, el único cambio que debe hacer es cambiar run () para llamar a run_single_process () en lugar de run_forked () y así terminará):

     import os,sys batchSize = 20 class Runner(object): def __init__(self,dataFeedGenerator,dataProcessor): self._dataFeed = dataFeedGenerator self._caller = dataProcessor def run(self): self.run_forked() def run_forked(self): dataFeed = self._dataFeed dataSubFeed = [] for i,dataMorsel in enumerate(dataFeed,1): if i % batchSize > 0: dataSubFeed.append(dataMorsel) else: self._dataFeed = dataSubFeed self.fork() dataSubFeed = [] if self._child_pid is 0: self.run_single_process() self.endBatch() def run_single_process(self) for dataMorsel in self._dataFeed: self._caller(dataMorsel) def fork(self): self._child_pid = os.fork() def endBatch(self): if self._child_pid is not 0: os.waitpid(self._child_pid, 0) else: sys.exit() # exit from the child when done 

    Esto aísla la pérdida de memoria al proceso hijo. Y nunca se filtrará más veces que el valor de la variable batchSize.

    Simplemente refactorizaré los experimentos en funciones individuales (si no es así ya) y luego aceptaré un número de experimento desde la línea de comandos que llama a la función de experimento individual.

    El solo bodgy un shell shell de la siguiente manera:

     #!/bin/bash for expnum in 1 2 3 4 5 6 7 8 9 10 11 ; do python youProgram ${expnum} otherParams done 

    De esa manera, puede dejar la mayor parte de su código tal como está y esto eliminará cualquier pérdida de memoria que crea que haya entre cada experimento.

    Por supuesto, la mejor solución siempre es encontrar y solucionar la causa raíz de un problema, pero, como ya ha dicho, esa no es una opción para usted.

    Aunque es difícil imaginar una pérdida de memoria en Python, confío en eso, sin embargo, es posible que desee considerar al menos la posibilidad de que esté equivocado allí. Considere plantear eso en una pregunta separada, algo en lo que podemos trabajar con baja prioridad (a diferencia de esta versión de corrección rápida).

    Actualización: Creación de wiki de la comunidad ya que la pregunta ha cambiado algo desde el original. Eliminaré la respuesta, pero por el hecho de que sigo pensando que es útil, usted podría hacer lo mismo con su corredor de experimentos, ya que le propuse el script de bash, solo debe asegurarse de que los experimentos sean procesos separados para que no se produzcan pérdidas de memoria. (Si las memory leaks están en el corredor, tendrá que hacer un análisis de la causa raíz y corregir el error).