2010-07-30 12 views
26

Tengo un programa que contiene una gran cantidad de objetos, muchos de ellos matrices Numpy. Mi programa 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.Asignación de memoria de perfil en Python (con soporte para matrices Numpy)

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

He oído cosas decentes acerca de Heapy, pero desafortunadamente Heapy no es compatible con matrices Numpy, y la mayor parte de mi programa implica matrices Numpy.

+0

no su (buena) pregunta, pero por supuesto puede utilizar una gran cantidad de dtype = np.float32 /. astype (np.float32) para flotantes de 32 bits en lugar de 64 bits. (Tenga cuidado con las funciones que silenciosamente hacen 32 en -> 64 fuera). – denis

Respuesta

0

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

+3

Bueno, sí, pero el objetivo de los perfiles es que quiero saber * qué * arreglos debo escribir en el archivo. Si trato de escribir todo y sacarlo solo cuando lo necesito, los requisitos de E/S serían enormes, y eso vencería el problema. – chimeracoder

10

Eche un vistazo a memory profiler. Proporciona línea por línea de perfilado y Ipython integración, lo que hace que sea muy fácil de usar que:

In [1]: import numpy as np 

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

actualización

Según lo mencionado por @WickedGrey parece que hay un error (see github issue tracker) cuando se llama a una más de una función de tiempo, 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é hasta qué punto los resultados tal vez influenciados (parece ser no tanto en mi ejemplo, por lo que de pendiente en su caso de uso, tal vez aún sea útil) y cuando este problema se solucione. Lo pregunté al github.

+0

https://github.com/fabianp/memory_profiler/issues/9 ("resultados incorrectos al llamar a la función dos veces") parece que esto hace que no sea un comienzo para cualquier aplicación seria. ¿Estoy malentendiendo el problema? –

+0

@WickedGrey Quizás tengas razón, nunca usé 'memory_profiler' antes, solo leí al respecto, pero puedo reproducir este error ahora. Actualizado mi respuesta. – bmu

+0

Dado que memory_profiler consulta que el sistema operativo tenga la cantidad de memoria utilizada, no es probable que tenga resultados ligeramente diferentes en diferentes ejecuciones, ya que puede estar contaminado por elementos como el historial de IPython o la forma en que Python asigna memoria (nunca se sabe con seguridad cuando esa memoria se vaya a liberar). Por lo tanto, una diferencia de menos del 1% en los resultados no me parece poco probable. –

11

Una forma de resolver el problema si llama a muchas funciones diferentes y no está seguro de dónde proviene el intercambio sería utilizar la nueva funcionalidad de trazado de memory_profiler. Primero debe decorar las diferentes funciones que está utilizando con @profile. Por simplicidad usaré el ejemplo examples/numpy_example.py enviado con memory_profiler que contiene dos funciones: create_data() y process_data()

Para ejecutar la secuencia de comandos, en lugar de ejecutarlo con el intérprete de Python, utiliza el ejecutable mprof, es decir

$ mprof run examples/numpy_example.py 

Esto creará un archivo llamado mprofile_??????????.dat, donde? sostendrá números que representan la fecha actual. Para trazar el resultado, simplemente escriba mprof plot y generará una trama similar a esto (si tiene varios archivos .dat que siempre tendrá la última):

output of memory_profiler's mprof

Aquí puede ver el consumo de memoria, con corchetes que indican cuándo ingresa/abandona la función actual. De esta forma, es fácil ver que la función process_data() tiene un pico de consumo de memoria. Para profundizar en su función, puede utilizar el generador de perfiles 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 le daría una salida similar a la siguiente:

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 es claro que scipy.signal.detrend está asignando una gran cantidad de memoria.

0

¿Has probado valgrind con la herramienta massif?

valgrind --tool=massif python yourscript.py 

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

ms_print massif.out.xxx | less 

que tiene todo tipo de información útil, pero la trama justo en el comienzo debe ser lo que estás buscando. Consulte también massif tutorial en la página de inicio de valgrind.

El uso de valgrind es bastante avanzado y puede haber formas más sencillas de hacer lo que está buscando.

1

Desde numpy 1.7 existe una semi incorporado en forma de rastrear las asignaciones de memoria:

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

+1

Santa mierda, eso es súper útil. ¡Gracias! Tenía una función cuyo uso máximo de memoria escalaba con el tamaño de sus datos de entrada por un factor de 12x. Pero ahora, gracias a esa secuencia de comandos 'track_allocations.py', la reduje a solo 4x, por lo que ya no veré esos molestos' MemoryError's. ¡Uf! Encantador tener eso en mi caja de herramientas ahora. –