Python-Trabajando alrededor de las fugas de memoria
memory-leaks (4)
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 programa, comienza leyendo ese archivo y, por lo tanto, sabe qué pruebas ya se han ejecutado. Esta arquitectura es más sólida porque también cubre el caso en el que el programa se bloquea durante una prueba; por supuesto, para automatizar completamente la recuperación de dichos bloqueos, querrá que un programa y proceso de vigilancia independiente se encargue de iniciar una nueva instancia del programa 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 programa de prueba podría significar que, mientras que cualquier falla o salida con un estado! = 0 significa la necesidad de comenzar una nueva instancia nueva).
Si estas arquitecturas son atractivas, pero necesita más ayuda para implementarlas, solo comente esta respuesta y me complacerá proporcionar el código de ejemplo. No quiero hacerlo "de forma preventiva" en caso de que haya problemas aún no expresados Eso hace que las arquitecturas sean inadecuadas para ti. (También podría ayudar saber qué plataformas necesita para ejecutar).
Tengo un programa 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 programa 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 simple bucle for 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 en realidad me ha permitido descubrir que mis objetos ClosenessCalculation 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 fugas de memoria
>>> 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]]
[(<type ''list''>, 11)]
11 porque hemos filtrado una lista que contiene 10 listas más
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ía la respuesta, pero por el hecho de que sigo pensando que es útil, 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 fugas de memoria están en el corredor, tendrá que hacer un análisis de la causa raíz y corregir el error).
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.