Las aplicaciones Python, especialmente las que trabajan con grandes conjuntos de datos o cálculos complejos, pueden consumir demasiada memoria, provocando ralentizaciones, bloqueos o errores por falta de memoria. El perfilado de memoria es una técnica que te permite analizar el uso de memoria de tu código Python, identificar fugas de memoria y cuellos de botella, y optimizar el consumo de memoria para mejorar el rendimiento y la estabilidad.
En este tutorial, exploraremos el perfilado de memoria, las herramientas disponibles y ejemplos prácticos de cómo utilizar estas herramientas para perfilar tu código Python.
¿Qué es el perfilado de memoria?
El perfilado de memoria es la técnica de monitorizar y evaluar la utilización de memoria de un programa mientras se ejecuta. Este método ayuda a los desarrolladores a encontrar fugas de memoria, optimizar la utilización de la memoria y comprender los patrones de consumo de memoria de sus programas. El perfilado de la memoria es crucial para evitar que las aplicaciones utilicen más memoria de la necesaria y provoquen un rendimiento lento o fallos.
Ventajas del perfilado de memoria
La creación de perfiles de memoria ofrece varias ventajas clave que son esenciales para mantener aplicaciones eficientes y robustas:
Identifica las fugas de memoria: Encuentra secciones de código que no estén liberando memoria correctamente.
Optimiza el uso de la memoria: Localiza las funciones u objetos que utilizan demasiada memoria.
Mejora el rendimiento: Mejora la velocidad y la capacidad de respuesta de las aplicaciones.
Evita las colisiones: Evita errores por falta de memoria.
Herramientas populares de perfilado de memoria
Exploremos varias herramientas populares de perfilado de memoria, detallando sus características, instalación y uso. En concreto, veremos herramientas como memory_profiler para analizar la memoria línea por línea, tracemalloc para tomar y comparar instantáneas de memoria, y objgraph para visualizar las relaciones entre objetos.
memory_profiler
memory_profiler es una potente herramienta de Python que proporciona una visión detallada del uso de memoria de tu código. Funciona proporcionando un desglose granular, línea por línea, del uso de memoria, lo que resulta útil para localizar las líneas exactas de código responsables de un elevado consumo de memoria. Al comprender el uso de la memoria con este nivel de detalle, los desarrolladores pueden tomar decisiones informadas sobre dónde centrar sus esfuerzos de optimización.
Instalación
Para instalar memory_profiler y los paquetes adicionales que sean necesarios, sigue estos pasos:
Abre el terminal o el símbolo del sistema.
En Windows, utilizaremos el Símbolo del sistema o PowerShell.
Podemos utilizar el terminal en MacOS o Linux.
El siguiente comando instala el paquete memory_profiler:
Abre tu script Python en un editor de texto o IDE.
Importa el decorador de perfiles de memory_profiler y aplícalo a las funciones que quieras perfilar.
Aquí tienes un script de ejemplo:
from memory_profiler import profile
@profiledefallocate_memory():# Allocate a list with a range of numbers
a =[i for i inrange(10000)]# Allocate another list with squares of numbers
b =[i **2for i inrange(10000)]return a, b
if __name__ =="__main__":
allocate_memory()Con la tecnología de
Explicación del código
Aquí importo el decorador de perfiles del paquete memory_profiler. Este decorador nos ayudará a controlar el uso de memoria de la función decorada.
Aplico el decorador @profile a la función allocate_memory(). Esto significa que cuando se ejecute allocate_memory(), memory_profiler hará un seguimiento de su uso de memoria y proporcionará un informe detallado.
Dentro de la función allocate_memory(), creo dos listas. La primera lista contiene números del 0 al 9999; la segunda lista contiene los cuadrados de los números del 0 al 9999.
a =[i for i inrange(10000)]# List of numbers from 0 to 9999
b =[i**2for i inrange(10000)]# List of squares of numbers from 0 to 9999Con la tecnología de
El siguiente bloque garantiza que sólo se llame a la función allocate_memory() cuando se ejecute directamente el script, no cuando se importe como módulo. Cuando ejecutes este script, memory_profiler mostrará el uso de memoria de la función allocate_memory(), lo que te permitirá ver cuánta memoria se utiliza durante la creación de las dos listas, a y b.
Uso de memoria de cada línea de código. Imagen del autor.
En el gráfico anterior, podemos ver el uso de memoria de cada línea de código y los incrementos de cada línea de código. El gráfico siguiente muestra con más detalle cómo utiliza la memoria cada línea y cómo aumenta con cada iteración.
Uso de la memoria a lo largo del tiempo. Imagen del autor.
tracemalloc
tracemalloc es un potente módulo integrado en Python que realiza un seguimiento de las asignaciones y desasignaciones de memoria. Comprender cómo utiliza la memoria tu aplicación es crucial para optimizar el rendimiento e identificar las fugas de memoria. Tracemalloc proporciona una interfaz fácil de usar para capturar y analizar instantáneas del uso de la memoria, lo que la convierte en una herramienta inestimable para cualquier desarrollador de Python.
Examinemos cómo tracemalloc toma instantáneas del uso de memoria, compara esas instantáneas para identificar fugas de memoria y, a continuación, analiza los principales consumidores de memoria
Tomar instantáneas del uso de la memoria
tracemalloc te permite tomar instantáneas del uso de memoria en puntos concretos de tu programa. Una instantánea captura el estado de las asignaciones de memoria, proporcionando una visión detallada de dónde se utiliza la memoria.
A continuación te explicamos cómo puedes hacer una instantánea con tracemalloc:
import tracemalloc
# Start tracing memory allocations
tracemalloc.start()# Code block for which memory usage is to be trackeddefallocate_memory():
a =[i for i inrange(10000)]# List of numbers from 0 to 9999
b =[i**2for i inrange(10000)]# List of squares of numbers from 0 to 9999return a, b
# Keep the allocated lists in scope
allocated_lists = allocate_memory()# Take a snapshot
snapshot = tracemalloc.take_snapshot()# Stop tracing memory allocations
tracemalloc.stop()# Analyze the snapshot to see memory usage
top_stats = snapshot.statistics('lineno')# Write the memory usage data to a text filewithopen('memory_usage.txt','w')as f:
f.write("[ Top 10 memory consumers ]\n")for stat in top_stats[:10]:
f.write(f"{stat}\n")# Detailed traceback for the top memory consumer
f.write("\n[ Detailed traceback for the top memory consumer ]\n")for stat in top_stats[:1]:
f.write('\n'.join(stat.traceback.format())+'\n')print("Memory usage details saved to 'memory_usage.txt'")Con la tecnología de
He rastreado y examinado el uso de memoria en mi aplicación Python utilizando el módulo tracemalloc. Tomando una instantánea de las asignaciones de memoria y observando los principales consumidores de memoria, determiné qué secciones de mi código utilizaban más memoria. A continuación, pongo este análisis en un archivo de texto para su posterior lectura y referencia.
Comparar instantáneas para identificar fugas de memoria
Una de las funciones más potentes de tracemalloc es la posibilidad de comparar instantáneas tomadas en distintos puntos de tu programa para identificar fugas de memoria. Analizando las diferencias entre las instantáneas, puedes localizar dónde se está asignando memoria adicional y no se está liberando.
A continuación te explicamos cómo comparar instantáneas:
import tracemalloc
# Start tracing memory allocations
tracemalloc.start()# Function to allocate memorydefallocate_memory():
a =[i for i inrange(10000)]# List of numbers from 0 to 9999
b =[i**2for i inrange(10000)]# List of squares of numbers from 0 to 9999return a, b
# Initial memory usage snapshot
snapshot1 = tracemalloc.take_snapshot()# Perform memory allocations
allocated_lists = allocate_memory()# Memory usage snapshot after allocations
snapshot2 = tracemalloc.take_snapshot()# Further memory allocations (simulate more work to find potential leaks)
allocated_lists += allocate_memory()# Memory usage snapshot after additional allocations
snapshot3 = tracemalloc.take_snapshot()# Stop tracing memory allocations
tracemalloc.stop()# Compare the snapshots
stats1_vs_2 = snapshot2.compare_to(snapshot1,'lineno')
stats2_vs_3 = snapshot3.compare_to(snapshot2,'lineno')# Save the comparison results to a text filewithopen('memory_leak_analysis.txt','w')as f:
f.write("[ Memory usage increase from snapshot 1 to snapshot 2 ]\n")for stat in stats1_vs_2[:10]:
f.write(f"{stat}\n")
f.write("\n[ Memory usage increase from snapshot 2 to snapshot 3 ]\n")for stat in stats2_vs_3[:10]:
f.write(f"{stat}\n")# Detailed traceback for the top memory consumers
f.write("\n[ Detailed traceback for the top memory consumers ]\n")for stat in stats2_vs_3[:1]:
f.write('\n'.join(stat.traceback.format())+'\n')print("Memory leak analysis saved to 'memory_leak_analysis.txt'")Con la tecnología de
Pasando de la instantánea 1 a la instantánea 2, vemos que el establecimiento de las listas a y b fue la causa principal de un pico en el uso de memoria. Si pasamos de la instantánea 2 de sa la instantánea 3, también vemos que más asignaciones de las listas a y b aumentaron la utilización de memoria.
Comparando estas instantáneas, descubrimos que la causa principal del aumento del uso de memoria es la asignación recurrente de grandes listas. Estas asignaciones pueden indicar una fuga de memoria si no son necesarias en aplicaciones del mundo real. Un rastreo exhaustivo puede identificar las líneas de código precisas que causan estas asignaciones.
Análisis de los principales consumidores de memoria
tracemalloc también proporciona herramientas para analizar los principales consumidores de memoria de tu aplicación. Esto puede ayudarte a centrarte en optimizar las partes de tu código que utilizan más memoria.
He aquí cómo analizar a los principales consumidores de memoria:
import tracemalloc
# Start tracing memory allocations
tracemalloc.start()# Code block for which memory usage is to be trackeddefallocate_memory():
a =[i for i inrange(10000)]
b =[i**2for i inrange(10000)]
allocate_memory()# Take a snapshot
snapshot = tracemalloc.take_snapshot()# Analyze top memory consumers
top_stats = snapshot.statistics('lineno')print("[ Top 10 memory consumers ]")for stat in top_stats[:10]:print(stat)# Stop tracing memory allocations
tracemalloc.stop()Con la tecnología de
En este ejemplo, después de tomar una instantánea, utilizo instantánea. Estadísticas ('lineno') para obtener estadísticas sobre el uso de memoria agrupadas por número de línea. Este resultado te ayuda a identificar rápidamente qué partes de tu código consumen más memoria, lo que te permite centrarte en esas áreas para optimizarlas.
objgraph
Objgraph es una biblioteca de Python diseñada para ayudar a los desarrolladores a visualizar y analizar las referencias a objetos en su código. Proporciona herramientas para crear gráficos visuales que muestran cómo los objetos de la memoria se referencian entre sí. Esta visualización es beneficiosa para comprender la estructura y las relaciones dentro de tu programa, identificar fugas de memoria y optimizar el uso de la memoria.
Instalación
En primer lugar, vamos a instalar la biblioteca objgraph. Puedes instalarlo con pip:
objgraph ofrece varias funciones críticas para el perfilado y la visualización de la memoria:
Generar Gráficos Interactivos de Referencias a Objetos: Conobjgraph, puedes crear representaciones visuales de objetos y sus referencias. Esto te ayuda a comprender cómo están interconectados los objetos, haciendo más accesible la depuración y optimización de tu código.
Detectar referencias circulares: Las referencias circulares se producen cuando dos o más objetos se referencian entre sí, creando un bucle que el recolector de basura no puede resolver. Objgraph ayuda a identificar estas referencias circulares, que pueden provocar fugas de memoria si no se abordan.
Analizar tipos y tamaños de objetos: Objgraph te permite examinar los tipos y tamaños de los objetos en memoria. Esta información es crucial para optimizar el uso de la memoria y mejorar el rendimiento de tu aplicación.
Utilizar objgraph para visualizar las relaciones entre objetos
Vamos a sumergirnos en algunos ejemplos para ver cómo se puede utilizar objgraph en la práctica.
Ejemplo 1: Generar un gráfico de referencia de objetos
Supongamos que tenemos una clase sencilla y creamos algunas instancias:
classNode:def__init__(self, value):
self.value = value
self.children =[]# Create some nodes
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)# Establish relationships
node1.children.append(node2)
node2.children.append(node3)import Objgraph
# Generate a graph of object references
objgraph.show_refs([node1], filename='object_refs.png')Con la tecnología de
En este ejemplo, definimos una clase Node y creamos instancias de nodos con relaciones. La función objgraph.show_refs() genera un gráfico visual de las referencias a objetos a partir de node1 y lo guarda como object_refs.png. Este gráfico nos ayuda a comprender cómo están interconectados los nodos.
Ejemplo 2: Detectar referencias circulares
Para detectar referencias circulares, podemos utilizar la función objgraph.find_backref_chain():
# Create a circular reference
node 3.children.append(node1)# Detect circular references
chain = objgraph.find_backref_chain(node1, objgraph.is_proper_module)
objgraph.show_chain(chain, filename='circular_refs.png')Con la tecnología de
Aquí creamos una referencia circular añadiendo el nodo1 a los hijos del nodo3. La función objgraph.find_backref_chain() detecta esta referencia circular, y objgraph.show_chain la visualiza, guardando el resultado como circular_refs.png.
Ejemplo 3: Analizar tipos y tamaños de objetos
Para analizar los tipos y tamaños de los objetos, podemos utilizar objgraph.show_most_common_types y objgraph.by_type:
# Show the most common object types
objgraph.show_most_common_types()# Get the details of a specific object type
objs = objgraph.by_type('Node')
objgraph.show_refs(objs[:3], refcounts=True, filename='node_details.png')Con la tecnología de
En este ejemplo, objgraph.mostrar_tipos_más_comunes muestra los tipos de objetos más comunes que hay actualmente en memoria. La función objgraph.by_type() recupera todas las instancias de la clase Nodo, y visualizamos sus referencias, incluido el recuento de referencias, generando otro gráfico guardado como detalles_nodo.png.
Cómo utilizar las herramientas de creación de perfiles de memoria
Veamos ahora varias técnicas para utilizar eficazmente las herramientas de perfilado de memoria. Nos centraremos en el perfilado de funciones específicas, el seguimiento del uso general de memoria y la visualización de gráficos de objetos.
Perfilar funciones específicas
Dirígete a funciones específicas con memory_profiler o tracemalloc.
Ejemplo con memory_profiler
memory_profiler ofrece una forma sencilla de controlar el consumo de memoria línea por línea dentro de tus funciones, ayudándote a identificar las áreas en las que se puede reducir el uso de memoria.
from memory_profiler import profile
@profiledefallocate_memory():
a =[i for i inrange(10000)]
b =[i **2for i inrange(10000)]return a, b
if __name__ =="__main__":
allocate_memory()Con la tecnología de
Ejemplo con tracemalloc
import tracemalloc
tracemalloc.start()defallocate_memory():
a =[i for i inrange(10000)]
b =[i **2for i inrange(10000)]return a, b
allocate_memory()
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')for stat in top_stats[:10]:print(stat)Con la tecnología de
Seguimiento del uso global de la memoria
Para controlar el uso total de memoria de nuestro programa a lo largo del tiempo, podemos utilizar el módulo incorporado tracemalloc. He aquí cómo podemos seguir el uso general de la memoria e interpretar los resultados para identificar tendencias.
Pasos a seguir tracemalloc
Comenzamosomenzamos inicializando el rastreo de memoria al inicio de nuestro script.
A continuación, executa nuestro código ejecutando las secciones de nuestro código que queremos controlar.
defrun_heavy_computation():
a =[i for i inrange(10000)]
b =[i**2for i inrange(10000)]return a, b
run_heavy_computation()Con la tecnología de
Enhacemos instantáneas del uso de la memoria. Esto nos permite capturar el estado de las asignaciones de memoria en diferentes puntos.
snapshot1 = tracemalloc.take_snapshot()
run_heavy_computation()
snapshot2 = tracemalloc.take_snapshot()Con la tecnología de
Por último, comparamos instantáneas para comprender los patrones de uso de la memoria y detectar fugas.
stats = snapshot2.compare_to(snapshot1, 'lineno')
for stat in stats[:10]:
print(stat)Con la tecnología de
Ejemplo
import tracemalloc
# Start tracing memory allocations
tracemalloc.start()# Function to run heavy computationdefrun_heavy_computation():
a =[i for i inrange(10000)]
b =[i**2for i inrange(10000)]return a, b
# Initial memory usage snapshot
snapshot1 = tracemalloc.take_snapshot()# Perform memory allocations
run_heavy_computation()# Memory usage snapshot after allocations
snapshot2 = tracemalloc.take_snapshot()# Compare the snapshots
stats = snapshot2.compare_to(snapshot1,'lineno')# Print the top memory-consuming lines of codeprint("[ Top 10 memory consumers ]")for stat in stats[:10]:print(stat)# Stop tracing memory allocations
tracemalloc.stop()Con la tecnología de
Consejos para interpretar los resultados
Aquí tienes algunos consejos que te ayudarán a dar sentido a los datos de tus perfiles:
Busca cambios significativos: Céntrate en líneas con incrementos de memoria sustanciales.
Identifica los puntos calientes: Determina qué líneas o funciones consumen sistemáticamente más memoria.
Seguimiento en el tiempo: Toma varias instantáneas en distintas fases del programa para identificar tendencias y señalar cuándo y dónde se producen picos de uso de memoria.
Comprueba si hay fugas de memoria: Compara las instantáneas tomadas antes y después de operaciones significativas para detectar fugas. Los aumentos persistentes del uso de memoria suelen indicar fugas.
Visualizar gráficos de objetos
Utilizando la biblioteca objgraph, podemos crear visualizaciones de las relaciones entre objetos. Esto nos ayuda a analizar el uso de la memoria, detectar fugas y comprender el tiempo de vida de los objetos.
He aquí el proceso paso a paso para visualizar gráficos de objetos. Primero, generaremos un gráfico de referencias a objetos; después, detectaremos las referencias circulares; por último, analizaremos los gráficos.
Generar un gráfico de referencia de objetos
import objgraph
classNode:def__init__(self, value):
self.value = value
self.children =[]# Create some nodes and establish relationships
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node1.children.append(node2)
node2.children.append(node3)# Generate a graph of object references
objgraph.show_refs([node1], filename='object_refs.png')Con la tecnología de
Detectar referencias circulares
# Create a circular reference
node3.children.append(node1)
# Detect circular references
chain = objgraph.find_backref_chain(node1, objgraph.is_proper_module)
objgraph.show_chain(chain, filename='circular_refs.png')Con la tecnología de
Analizar tipos y tamaños de objetos
# Show the most common object types
objgraph.show_most_common_types()# Get details of a specific object type
nodes = objgraph.by_type('Node')
objgraph.show_refs(nodes[:3], refcounts=True, filename='node_details.png')Con la tecnología de
Ejemplo
import objgraph
classNode:def__init__(self, value):
self.value = value
self.children =[]# Create some nodes and establish relationships
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node1.children.append(node2)
node2.children.append(node3)# Generate a graph of object references
objgraph.show_refs([node1], filename='object_refs.png')# Create a circular reference
node3.children.append(node1)# Detect circular references
chain = objgraph.find_backref_chain(node1, objgraph.is_proper_module)
objgraph.show_chain(chain, filename='circular_refs.png')# Show the most common object types
objgraph.show_most_common_types()# Get details of a specific object type
nodes = objgraph.by_type('Node')
objgraph.show_refs(nodes[:3], refcounts=True, filename='node_details.png')Con la tecnología de
Analiza los gráficos
Como paso final, analizamos los gráficos. Consideramos los siguientes pasos en orden:
Identifica las conexiones: Observa cómo se conectan los objetos para comprender las dependencias y los tiempos de vida.
Detecta fugas: Encuentra objetos que deberían haberse recogido de la basura, pero que siguen en memoria, lo que indica posibles fugas.
Referencias circulares puntuales: Identifica las referencias circulares que pueden impedir la recogida de basuras y provocar la sobrecarga de memoria.
Optimiza el uso de la memoria: Utiliza la información de los gráficos para refactorizar nuestro código y mejorar la eficiencia de la memoria.
Consejos prácticos y buenas prácticas
He aquí algunos consejos clave y buenas prácticas que te ayudarán a sacar el máximo partido a tus esfuerzos de elaboración de perfiles:
Perfil Temprano: Perfila pronto y a menudo en el proceso de desarrollo.
Utiliza datos realistas: Utiliza datos y cargas de trabajo realistas para elaborar perfiles precisos.
Combina herramientas: Combina varias herramientas para una comprensión global.
Cuidado con los falsos positivos: Ten cuidado con los posibles falsos positivos en los perfiles de memoria.
Optimiza las partes que consumen mucha memoria: Céntrate en optimizar las partes de tu código que consumen más memoria.
Conclusión
En este tutorial, hemos explorado herramientas y técnicas esenciales para comprender y optimizar el uso de la memoria en tus aplicaciones Python. Estas técnicas incluían memory_profiler, que ofrece una forma directa de controlar el consumo de memoria línea por línea dentro de tu función, y objgraph, que proporciona potentes capacidades de visualización, permitiéndote analizar las relaciones entre objetos, detectar referencias circulares y comprender la estructura de tu uso de memoria.
Integrar herramientas de creación de perfiles de memoria en tu flujo de trabajo de desarrollo te permite obtener información valiosa sobre cómo gestiona la memoria tu código, te ayuda a tomar decisiones de optimización informadas y a mejorar el rendimiento y la fiabilidad generales de tus aplicaciones. Tanto si trabajas en pequeños scripts como en sistemas a gran escala, el perfilado de memoria es una práctica esencial para cualquier desarrollador de Python que pretenda escribir código eficiente y robusto.
Para profundizar tus conocimientos y habilidades en el perfilado de memoria y la escritura de código eficiente, considera la posibilidad de realizar el Curso de Escritura de Código Eficiente en Python. Además, nuestro curso de Programación en Python ofrece una visión completa de las mejores prácticas y técnicas avanzadas para ayudarte a convertirte en un desarrollador competente.
Aprende a gestionar hilos y procesos con el módulo de multiprocesamiento de Python. Descubre las técnicas clave de la programación paralela. Mejora la eficacia de tu código con ejemplos.
Este tutorial muestra cómo utilizar Matplotlib, una potente biblioteca de visualización de datos en Python, para crear gráficos de líneas, barras y dispersión con datos bursátiles.
Descubre cómo escribir funciones en Python reutilizables y eficientes. Domina los parámetros, las sentencias return y temas avanzados como las funciones lambda. Organiza mejor tu código con main() y otras buenas prácticas.
Las aplicaciones Python, especialmente las que trabajan con grandes conjuntos de datos o cálculos complejos, pueden consumir demasiada memoria, provocando ralentizaciones, bloqueos o errores por falta de memoria. El perfilado de memoria es una técnica que te permite analizar el uso de memoria de tu código Python, identificar fugas de memoria y cuellos de botella, y optimizar el consumo de memoria para mejorar el rendimiento y la estabilidad.
En este tutorial, exploraremos el perfilado de memoria, las herramientas disponibles y ejemplos prácticos de cómo utilizar estas herramientas para perfilar tu código Python.
¿Qué es el perfilado de memoria?
El perfilado de memoria es la técnica de monitorizar y evaluar la utilización de memoria de un programa mientras se ejecuta. Este método ayuda a los desarrolladores a encontrar fugas de memoria, optimizar la utilización de la memoria y comprender los patrones de consumo de memoria de sus programas. El perfilado de la memoria es crucial para evitar que las aplicaciones utilicen más memoria de la necesaria y provoquen un rendimiento lento o fallos.
Ventajas del perfilado de memoria
La creación de perfiles de memoria ofrece varias ventajas clave que son esenciales para mantener aplicaciones eficientes y robustas:
Herramientas populares de perfilado de memoria
Exploremos varias herramientas populares de perfilado de memoria, detallando sus características, instalación y uso. En concreto, veremos herramientas como
memory_profilerpara analizar la memoria línea por línea,tracemallocpara tomar y comparar instantáneas de memoria, yobjgraphpara visualizar las relaciones entre objetos.memory_profiler
memory_profileres una potente herramienta de Python que proporciona una visión detallada del uso de memoria de tu código. Funciona proporcionando un desglose granular, línea por línea, del uso de memoria, lo que resulta útil para localizar las líneas exactas de código responsables de un elevado consumo de memoria. Al comprender el uso de la memoria con este nivel de detalle, los desarrolladores pueden tomar decisiones informadas sobre dónde centrar sus esfuerzos de optimización.Instalación
Para instalar
memory_profilery los paquetes adicionales que sean necesarios, sigue estos pasos:El siguiente comando instala el paquete
memory_profiler:Si quieres visualizar el uso de memoria con gráficos, puedes instalar
matplotlibejecutando:Utilización
Para utilizar
memory_profiler, sigue estos pasos:Decoramos las funciones que queremos perfilar.
Abre tu script Python en un editor de texto o IDE.
Importa el decorador de perfiles de
memory_profilery aplícalo a las funciones que quieras perfilar.Aquí tienes un script de ejemplo:
Explicación del código
Aquí importo el decorador de perfiles del paquete
memory_profiler. Este decorador nos ayudará a controlar el uso de memoria de la función decorada.Aplico el decorador
@profilea la funciónallocate_memory(). Esto significa que cuando se ejecuteallocate_memory(),memory_profilerhará un seguimiento de su uso de memoria y proporcionará un informe detallado.Dentro de la función
allocate_memory(), creo dos listas. La primera lista contiene números del 0 al 9999; la segunda lista contiene los cuadrados de los números del 0 al 9999.El siguiente bloque garantiza que sólo se llame a la función
allocate_memory()cuando se ejecute directamente el script, no cuando se importe como módulo. Cuando ejecutes este script,memory_profilermostrará el uso de memoria de la funciónallocate_memory(), lo que te permitirá ver cuánta memoria se utiliza durante la creación de las dos listas,ayb.En tu terminal o símbolo del sistema, navega hasta el directorio donde se encuentra tu script. Ejecuta tu script con la opción
-m memory_profiler.Uso de memoria de cada línea de código. Imagen del autor.
En el gráfico anterior, podemos ver el uso de memoria de cada línea de código y los incrementos de cada línea de código. El gráfico siguiente muestra con más detalle cómo utiliza la memoria cada línea y cómo aumenta con cada iteración.
Uso de la memoria a lo largo del tiempo. Imagen del autor.
tracemalloc
tracemalloces un potente módulo integrado en Python que realiza un seguimiento de las asignaciones y desasignaciones de memoria. Comprender cómo utiliza la memoria tu aplicación es crucial para optimizar el rendimiento e identificar las fugas de memoria. Tracemalloc proporciona una interfaz fácil de usar para capturar y analizar instantáneas del uso de la memoria, lo que la convierte en una herramienta inestimable para cualquier desarrollador de Python.Examinemos cómo
tracemalloctoma instantáneas del uso de memoria, compara esas instantáneas para identificar fugas de memoria y, a continuación, analiza los principales consumidores de memoriaTomar instantáneas del uso de la memoria
tracemallocte permite tomar instantáneas del uso de memoria en puntos concretos de tu programa. Una instantánea captura el estado de las asignaciones de memoria, proporcionando una visión detallada de dónde se utiliza la memoria.A continuación te explicamos cómo puedes hacer una instantánea con
tracemalloc:He rastreado y examinado el uso de memoria en mi aplicación Python utilizando el módulo
tracemalloc. Tomando una instantánea de las asignaciones de memoria y observando los principales consumidores de memoria, determiné qué secciones de mi código utilizaban más memoria. A continuación, pongo este análisis en un archivo de texto para su posterior lectura y referencia.Comparar instantáneas para identificar fugas de memoria
Una de las funciones más potentes de
tracemalloces la posibilidad de comparar instantáneas tomadas en distintos puntos de tu programa para identificar fugas de memoria. Analizando las diferencias entre las instantáneas, puedes localizar dónde se está asignando memoria adicional y no se está liberando.A continuación te explicamos cómo comparar instantáneas:
Pasando de la instantánea 1 a la instantánea 2, vemos que el establecimiento de las listas
aybfue la causa principal de un pico en el uso de memoria. Si pasamos de la instantánea 2 de sa la instantánea 3, también vemos que más asignaciones de las listasaybaumentaron la utilización de memoria.Comparando estas instantáneas, descubrimos que la causa principal del aumento del uso de memoria es la asignación recurrente de grandes listas. Estas asignaciones pueden indicar una fuga de memoria si no son necesarias en aplicaciones del mundo real. Un rastreo exhaustivo puede identificar las líneas de código precisas que causan estas asignaciones.
Análisis de los principales consumidores de memoria
tracemalloctambién proporciona herramientas para analizar los principales consumidores de memoria de tu aplicación. Esto puede ayudarte a centrarte en optimizar las partes de tu código que utilizan más memoria.He aquí cómo analizar a los principales consumidores de memoria:
En este ejemplo, después de tomar una instantánea, utilizo instantánea. Estadísticas ('lineno') para obtener estadísticas sobre el uso de memoria agrupadas por número de línea. Este resultado te ayuda a identificar rápidamente qué partes de tu código consumen más memoria, lo que te permite centrarte en esas áreas para optimizarlas.
objgraph
Objgraph es una biblioteca de Python diseñada para ayudar a los desarrolladores a visualizar y analizar las referencias a objetos en su código. Proporciona herramientas para crear gráficos visuales que muestran cómo los objetos de la memoria se referencian entre sí. Esta visualización es beneficiosa para comprender la estructura y las relaciones dentro de tu programa, identificar fugas de memoria y optimizar el uso de la memoria.
Instalación
En primer lugar, vamos a instalar la biblioteca
objgraph. Puedes instalarlo con pip:Características principales de
objgraphobjgraphofrece varias funciones críticas para el perfilado y la visualización de la memoria:objgraph, puedes crear representaciones visuales de objetos y sus referencias. Esto te ayuda a comprender cómo están interconectados los objetos, haciendo más accesible la depuración y optimización de tu código.Utilizar objgraph para visualizar las relaciones entre objetos
Vamos a sumergirnos en algunos ejemplos para ver cómo se puede utilizar
objgraphen la práctica.Ejemplo 1: Generar un gráfico de referencia de objetos
Supongamos que tenemos una clase sencilla y creamos algunas instancias:
En este ejemplo, definimos una clase
Nodey creamos instancias de nodos con relaciones. La funciónobjgraph.show_refs()genera un gráfico visual de las referencias a objetos a partir denode1y lo guarda comoobject_refs.png. Este gráfico nos ayuda a comprender cómo están interconectados los nodos.Ejemplo 2: Detectar referencias circulares
Para detectar referencias circulares, podemos utilizar la función
objgraph.find_backref_chain():Aquí creamos una referencia circular añadiendo el nodo1 a los hijos del nodo3. La función
objgraph.find_backref_chain()detecta esta referencia circular, y objgraph.show_chain la visualiza, guardando el resultado como circular_refs.png.Ejemplo 3: Analizar tipos y tamaños de objetos
Para analizar los tipos y tamaños de los objetos, podemos utilizar objgraph.show_most_common_types y objgraph.by_type:
En este ejemplo, objgraph.mostrar_tipos_más_comunes muestra los tipos de objetos más comunes que hay actualmente en memoria. La función
objgraph.by_type()recupera todas las instancias de la clase Nodo, y visualizamos sus referencias, incluido el recuento de referencias, generando otro gráfico guardado como detalles_nodo.png.Cómo utilizar las herramientas de creación de perfiles de memoria
Veamos ahora varias técnicas para utilizar eficazmente las herramientas de perfilado de memoria. Nos centraremos en el perfilado de funciones específicas, el seguimiento del uso general de memoria y la visualización de gráficos de objetos.
Perfilar funciones específicas
Dirígete a funciones específicas con
memory_profilerotracemalloc.Ejemplo con
memory_profilermemory_profilerofrece una forma sencilla de controlar el consumo de memoria línea por línea dentro de tus funciones, ayudándote a identificar las áreas en las que se puede reducir el uso de memoria.Ejemplo con
tracemallocSeguimiento del uso global de la memoria
Para controlar el uso total de memoria de nuestro programa a lo largo del tiempo, podemos utilizar el módulo incorporado tracemalloc. He aquí cómo podemos seguir el uso general de la memoria e interpretar los resultados para identificar tendencias.
Pasos a seguir
tracemallocComenzamosomenzamos inicializando el rastreo de memoria al inicio de nuestro script.
A continuación, executa nuestro código ejecutando las secciones de nuestro código que queremos controlar.
Enhacemos instantáneas del uso de la memoria. Esto nos permite capturar el estado de las asignaciones de memoria en diferentes puntos.
Por último, comparamos instantáneas para comprender los patrones de uso de la memoria y detectar fugas.
Ejemplo
Consejos para interpretar los resultados
Aquí tienes algunos consejos que te ayudarán a dar sentido a los datos de tus perfiles:
Visualizar gráficos de objetos
Utilizando la biblioteca
objgraph, podemos crear visualizaciones de las relaciones entre objetos. Esto nos ayuda a analizar el uso de la memoria, detectar fugas y comprender el tiempo de vida de los objetos.He aquí el proceso paso a paso para visualizar gráficos de objetos. Primero, generaremos un gráfico de referencias a objetos; después, detectaremos las referencias circulares; por último, analizaremos los gráficos.
Generar un gráfico de referencia de objetos
Detectar referencias circulares
Analizar tipos y tamaños de objetos
Ejemplo
Analiza los gráficos
Como paso final, analizamos los gráficos. Consideramos los siguientes pasos en orden:
Consejos prácticos y buenas prácticas
He aquí algunos consejos clave y buenas prácticas que te ayudarán a sacar el máximo partido a tus esfuerzos de elaboración de perfiles:
Conclusión
En este tutorial, hemos explorado herramientas y técnicas esenciales para comprender y optimizar el uso de la memoria en tus aplicaciones Python. Estas técnicas incluían
memory_profiler, que ofrece una forma directa de controlar el consumo de memoria línea por línea dentro de tu función, yobjgraph, que proporciona potentes capacidades de visualización, permitiéndote analizar las relaciones entre objetos, detectar referencias circulares y comprender la estructura de tu uso de memoria.Integrar herramientas de creación de perfiles de memoria en tu flujo de trabajo de desarrollo te permite obtener información valiosa sobre cómo gestiona la memoria tu código, te ayuda a tomar decisiones de optimización informadas y a mejorar el rendimiento y la fiabilidad generales de tus aplicaciones. Tanto si trabajas en pequeños scripts como en sistemas a gran escala, el perfilado de memoria es una práctica esencial para cualquier desarrollador de Python que pretenda escribir código eficiente y robusto.
Para profundizar tus conocimientos y habilidades en el perfilado de memoria y la escritura de código eficiente, considera la posibilidad de realizar el Curso de Escritura de Código Eficiente en Python. Además, nuestro curso de Programación en Python ofrece una visión completa de las mejores prácticas y técnicas avanzadas para ayudarte a convertirte en un desarrollador competente.