Asignación de memoria de perfil en Python (con soporte para matrices Numpy)

Tengo un progtwig que contiene una gran cantidad de objetos, muchos de ellos matrices Numpy. Mi progtwig se está intercambiando miserablemente, y estoy tratando de reducir el uso de la memoria, porque en realidad no puede terminar en mi sistema con los requisitos de memoria actuales.

Estoy buscando un buen generador de perfiles que me permita verificar la cantidad de memoria consumida por varios objetos (estoy imaginando una contraparte de memoria para cProfile) para saber dónde optimizar.

He escuchado cosas decentes sobre Heapy, pero lamentablemente Heapy no admite matrices Numpy, y la mayoría de mi progtwig involucra matrices Numpy.

Una forma de abordar el problema si está llamando a muchas funciones diferentes y no está seguro de dónde proviene el intercambio sería utilizar la nueva funcionalidad de trazado desde memory_profiler . Primero debes decorar las diferentes funciones que estás usando con @profile. Para simplificar, usaré los ejemplos de ejemplos / numpy_example.py incluido con memory_profiler que contiene dos funciones: create_data() y process_data()

Para ejecutar su script, en lugar de ejecutarlo con el intérprete de Python, use el ejecutable mprof, es decir

 $ mprof run examples/numpy_example.py 

Esto creará un archivo llamado mprofile_??????????.dat , donde? mantendrá números que representan la fecha actual. Para trazar el resultado, simplemente escriba mprof plot y generará un gráfico similar a este (si tiene varios archivos .dat siempre tomará el último):

salida de memory_profiler de mprof

Aquí puede ver el consumo de memoria, con paréntesis que indican cuándo ingresa / abandona la función actual. De esta manera, es fácil ver que la función process_data() tiene un pico de consumo de memoria. Para profundizar más en su función, puede usar el perfilador línea por línea para ver el consumo de memoria de cada línea en su función. Esto se ejecuta con

 python -m memory_profiler examples/nump_example.py 

Esto te daría una salida similar a esta:

 Line # Mem usage Increment Line Contents ================================================ 13 @profile 14 223.414 MiB 0.000 MiB def process_data(data): 15 414.531 MiB 191.117 MiB data = np.concatenate(data) 16 614.621 MiB 200.090 MiB detrended = scipy.signal.detrend(data, axis=0) 17 614.621 MiB 0.000 MiB return detrended 

donde está claro que scipy.signal.detrend está asignando una gran cantidad de memoria.

Echa un vistazo al perfilador de memoria . Proporciona perfiles línea por línea e integración con Ipython , lo que hace que sea muy fácil de usar:

 In [1]: import numpy as np In [2]: %memit np.zeros(1e7) maximum of 3: 70.847656 MB per loop 

Actualizar

Como mencionó @WickedGrey, parece que hay un error ( consulte el rastreador de problemas de github ) cuando se llama una función más de una vez, que puedo reproducir:

 In [2]: for i in range(10): ...: %memit np.zeros(1e7) ...: maximum of 1: 70.894531 MB per loop maximum of 1: 70.894531 MB per loop maximum of 1: 70.894531 MB per loop maximum of 1: 70.894531 MB per loop maximum of 1: 70.894531 MB per loop maximum of 1: 70.894531 MB per loop maximum of 1: 70.902344 MB per loop maximum of 1: 70.902344 MB per loop maximum of 1: 70.902344 MB per loop maximum of 1: 70.902344 MB per loop 

Sin embargo, no sé en qué medida pueden influir los resultados (parece que no lo es tanto en mi ejemplo, por lo que, dependiendo de su caso de uso, puede ser útil) y cuándo se puede solucionar este problema. Te lo pedí en github .

Desde numpy 1.7 existe una forma semi incorporada para rastrear las asignaciones de memoria:

https://github.com/numpy/numpy/tree/master/tools/allocation_tracking

¿Puede simplemente guardar / grabar algunos de los arreglos en el disco en archivos tmp cuando no los está usando? Eso es lo que he tenido que hacer en el pasado con grandes arreglos. Por supuesto, esto ralentizará el progtwig, pero al menos terminará. ¿A menos que los necesites todos a la vez?

¿Has probado valgrind con la herramienta de massif ?

 valgrind --tool=massif python yourscript.py 

creará un archivo llamado massif.out.xxx que puede inspeccionar a través de

 ms_print massif.out.xxx | less 

Tiene todo tipo de información útil, pero la ttwig desde el principio debe ser lo que está buscando. También puedes ver el tutorial de macizo en la página de inicio de valgrind.

El uso de valgrind es bastante avanzado y podría haber formas más fáciles de hacer lo que está buscando.