Introducción
La gestión de la memoria es el proceso de asignar, desasignar y coordinar eficientemente la memoria para que todos los diferentes procesos se ejecuten sin problemas y puedan acceder de forma óptima a los diferentes recursos del sistema. La gestión de la memoria también implica la limpieza de la memoria de los objetos a los que ya no se accede.
En Python, el gestor de memoria se encarga de este tipo de tareas ejecutándose periódicamente para limpiar, asignar y gestionar la memoria. A diferencia de C, Java y otros lenguajes de programación, Python gestiona los objetos utilizando el conteo de referencias. Esto significa que el gestor de memoria lleva la cuenta del número de referencias a cada objeto en el programa. Cuando el recuento de referencias de un objeto cae a cero, lo que significa que el objeto ya no se utiliza, el recolector de basura (parte del gestor de memoria) libera automáticamente la memoria de ese objeto en particular.
El usuario no tiene que preocuparse por la gestión de la memoria, ya que el proceso de asignación y desasignación de memoria es totalmente automático. La memoria recuperada puede ser utilizada por otros objetos.
Recogida de basura de Python
Como se ha explicado anteriormente, Python elimina los objetos a los que ya no se hace referencia en el programa para liberar espacio de memoria. Este proceso en el que Python libera bloques de memoria que ya no se usan se llama Recolección de Basura. El Garbage Collector (GC) de Python se ejecuta durante la ejecución del programa y se activa si el conteo de referencias se reduce a cero. La cuenta de referencias aumenta si a un objeto se le asigna un nuevo nombre o se coloca en un contenedor, como una tupla o un diccionario. Del mismo modo, el recuento de referencias disminuye cuando se reasigna la referencia a un objeto, cuando la referencia del objeto sale del ámbito o cuando se elimina un objeto.
La memoria es un montón que contiene objetos y otras estructuras de datos utilizadas en el programa. La asignación y desasignación de este espacio del heap es controlada por el gestor de memoria de Python mediante el uso de funciones de la API.
Objetos de Python en la memoria
Cada variable en Python actúa como un objeto. Los objetos pueden ser simples (que contienen números, cadenas, etc.) o contenedores (diccionarios, listas o clases definidas por el usuario). Además, Python es un lenguaje de tipado dinámico, lo que significa que no necesitamos declarar las variables ni sus tipos antes de utilizarlas en un programa.
Por ejemplo:
Si nos fijamos en las 2 primeras líneas del programa anterior, se conoce el objeto x
. Cuando borramos el objeto x
e intentamos utilizarlo, obtenemos un error indicando que la variable x
no está definida.
Puedes ver que la recolección de basura en Python está totalmente automatizada y el programador no necesita preocuparse por ella, a diferencia de lenguajes como C.
Modificación del recolector de basura
El recolector de basura de Python tiene tres generaciones en las que se clasifican los objetos. Un nuevo objeto en el punto inicial de su ciclo de vida es la primera generación del recolector de basura. A medida que el objeto sobrevive a la recolección de basura, será movido a las siguientes generaciones. Cada una de las 3 generaciones del recolector de basura tiene un umbral. En concreto, cuando se supera el umbral de número de asignaciones menos el número de desasignaciones, esa generación ejecutará la recogida de basura.
Las primeras generaciones también se recogen de la basura con más frecuencia que las generaciones superiores. Esto se debe a que los objetos más nuevos son más propensos a ser descartados que los objetos viejos.
El módulo gc
incluye funciones para cambiar el valor del umbral, desencadenar un proceso de recolección de basura manualmente, desactivar el proceso de recolección de basura, etc. Podemos comprobar los valores del umbral de las diferentes generaciones del recolector de basura utilizando el método get_threshold()
:
import gcprint(gc.get_threshold())
Muestra de salida:
(700, 10, 10)
Como ves, aquí tenemos un umbral de 700 para la primera generación, y 10 para cada una de las otras dos generaciones.
Podemos alterar el valor del umbral para desencadenar el proceso de recolección de basura utilizando el método set_threshold()
del módulo gc
:
gc.set_threshold(900, 15, 15)
En el ejemplo anterior, hemos aumentado el valor del umbral para las 3 generaciones. Aumentar el valor del umbral disminuirá la frecuencia de ejecución del recolector de basura. Normalmente, no necesitamos pensar demasiado en la recolección de basura de Python como desarrollador, pero esto puede ser útil cuando se optimiza el tiempo de ejecución de Python para su sistema de destino. Uno de los principales beneficios es que el mecanismo de recolección de basura de Python maneja muchos detalles de bajo nivel para el desarrollador de forma automática.
¿Por qué realizar la recolección de basura manual?
Sabemos que el intérprete de Python mantiene un registro de las referencias a los objetos utilizados en un programa. En versiones anteriores de Python (hasta la versión 1.6), el intérprete de Python utilizaba únicamente el mecanismo de recuento de referencias para gestionar la memoria. Cuando el recuento de referencias llega a cero, el intérprete de Python libera automáticamente la memoria. Este mecanismo clásico de conteo de referencias es muy efectivo, excepto que no funciona cuando el programa tiene ciclos de referencia. Un ciclo de referencia ocurre si uno o más objetos son referenciados entre sí, y por lo tanto el conteo de referencias nunca llega a cero.
Consideremos un ejemplo.
>>> def create_cycle():... list = ... list.append(list)... return list... >>> create_cycle()]
El código anterior crea un ciclo de referencia, donde el objeto list
se refiere a sí mismo. Por lo tanto, la memoria del objeto list
no se liberará automáticamente cuando la función regrese. El problema del ciclo de referencia no puede ser resuelto por el conteo de referencias. Sin embargo, este problema del ciclo de referencias se puede resolver cambiando el comportamiento del recolector de basura en tu aplicación Python.
Para ello, podemos utilizar la función gc.collect()
del módulo gc
.
import gcn = gc.collect()print("Number of unreachable objects collected by GC:", n)
La función gc.collect()
devuelve el número de objetos que ha recogido y desasignado.
Hay dos maneras de realizar la recolección de basura manual: recolección de basura basada en el tiempo o basada en eventos.
La recolección de basura basada en el tiempo es bastante simple: la función gc.collect()
se llama después de un intervalo de tiempo fijo.
La recolección de basura basada en eventos llama a la función gc.collect()
después de que se produzca un evento (es decir, cuando se sale de la aplicación o la aplicación permanece inactiva durante un período de tiempo específico).
Entendamos el trabajo de recolección de basura manual creando unos cuantos ciclos de referencia.
La salida es la siguiente:
Creating garbage...Collecting...Number of unreachable objects collected by GC: 8Uncollectable garbage:
El script anterior crea un objeto de lista que es referido por una variable, llamada creativamente list
. El primer elemento del objeto lista se refiere a sí mismo. El recuento de referencias del objeto lista es siempre mayor que cero, incluso si se borra o está fuera del ámbito del programa. Por lo tanto, el objeto list
no se recoge de la basura debido a la referencia circular. El mecanismo de recolección de basura en Python comprobará automáticamente, y recogerá, las referencias circulares periódicamente.
En el código anterior, como el recuento de referencias es al menos 1 y nunca puede llegar a 0, hemos forzado la recolección de basura de los objetos llamando a gc.collect()
. Sin embargo, recuerde no forzar la recolección de basura con frecuencia. La razón es que incluso después de liberar la memoria, el GC tarda en evaluar la elegibilidad del objeto para ser recogido de la basura, ocupando tiempo y recursos del procesador. Además, recuerda gestionar manualmente el recolector de basura sólo después de que tu aplicación se haya iniciado completamente.
Conclusión
En este artículo, hemos discutido cómo la gestión de la memoria en Python se maneja automáticamente mediante el uso de estrategias de conteo de referencias y recolección de basura. Sin la recolección de basura, la implementación de un mecanismo de gestión de memoria exitoso en Python es imposible. Además, los programadores no deben preocuparse por borrar la memoria asignada, ya que el gestor de memoria de Python se encarga de ello. Esto conduce a menos fugas de memoria y a un mejor rendimiento.