Hola a todos! Así terminó el largo fin de semana de marzo. Queremos dedicar la primera publicación posterior a las vacaciones a la amada por muchos cursos:
"Python Developer" , que comienza en menos de 2 semanas. Vamos
Contenido- La memoria es un libro vacio
- Gestión de memoria: del hardware al software
- Implementación base de Python
- Concepto de bloqueo de intérprete global (GIL)
- Recolector de basura
- Gestión de memoria en CPython:
- Conclusión

¿Alguna vez te has preguntado cómo procesa Python tus datos entre bastidores? ¿Cómo se almacenan sus variables en la memoria? ¿En qué punto se eliminan?
En este artículo, profundizaremos en la estructura interna de Python para comprender cómo funciona la administración de memoria.
Después de leer este artículo, usted:
- Obtenga más información sobre las operaciones de bajo nivel, especialmente la memoria.
- Comprenda cómo Python abstrae las operaciones de bajo nivel.
- Aprenda sobre algoritmos de administración de memoria en Python.
Conocer la estructura interna de Python proporcionará una mejor comprensión de los principios de su comportamiento. Espero que puedas echar un vistazo a Python desde una nueva perspectiva. Detrás de escena, hay muchas operaciones lógicas para que su programa funcione correctamente.
La memoria es un libro vacioPuedes imaginar la memoria de la computadora como un libro vacío, esperando que escriba muchas historias cortas. Todavía no hay nada en sus páginas, pero pronto aparecerán autores que quieran escribir sus historias en él. Para hacer esto, necesitarán un lugar.
Como no pueden escribir una historia encima de otra, deben tener mucho cuidado con las páginas en las que escriben. Antes de comenzar a escribir, consultan con el administrador del libro. El gerente decide en qué parte del libro los autores pueden escribir su historia.
Dado que el libro ha existido durante muchos años, muchas historias en él quedan desactualizadas. Cuando nadie lee o aborda el historial, lo eliminan para dar paso a nuevas historias.
En esencia, la memoria de la computadora es como un libro vacío. Los bloques continuos de memoria de una longitud fija generalmente se llaman páginas, por lo que esta analogía es útil.
Los autores pueden ser varias aplicaciones o procesos que necesitan almacenar datos en la memoria. Un gerente que decide dónde los autores pueden escribir sus historias desempeña el papel de un administrador de memoria, un clasificador. Y el que borra viejas historias es un recolector de basura.
Gestión de memoria: del hardware al softwareLa gestión de la memoria es el proceso en el que las aplicaciones de software leen y escriben datos. El administrador de memoria determina dónde colocar los datos del programa. Como la cantidad de memoria es, por supuesto, como el número de páginas en el libro, en consecuencia, el administrador necesita encontrar espacio libre para que la aplicación pueda usarla. Este proceso se llama "asignación de memoria".
Por otro lado, cuando los datos ya no son necesarios, se pueden eliminar. En este caso, hablan de liberar memoria. ¿Pero de qué se libera y de dónde viene?
En algún lugar dentro de la computadora hay un dispositivo físico que almacena datos cuando ejecuta programas de Python. El código Python pasa por muchos niveles de abstracción antes de llegar a este dispositivo.
Uno de los niveles principales que se encuentran por encima del equipo (RAM, disco duro, etc.) es el sistema operativo. Gestiona las solicitudes de lectura y escritura en la memoria.
Encima del sistema operativo hay una capa de aplicación en la que hay una de las implementaciones de Python (conectada a su sistema operativo o descargada de python.org). La gestión de la memoria para el código en este lenguaje de programación está regulada por herramientas especiales de Python. Los algoritmos y las estructuras que Python usa para administrar la memoria son el tema principal de este artículo.
Implementación base de PythonLa implementación base de Python, o "Python puro", es CPython escrito en C.
Me sorprendió mucho cuando lo escuché por primera vez. ¿Cómo se puede escribir un idioma en otro idioma? Bueno, no literalmente, por supuesto, pero la idea es algo como esto.
El lenguaje Python se describe en un
manual de referencia especial en inglés . Sin embargo, esta guía por sí sola no es muy útil. Aún necesita una herramienta para interpretar el código escrito por las reglas del directorio.
También necesitará algo para ejecutar el código en su computadora. La implementación básica de Python proporciona ambas condiciones. Convierte el código Python en instrucciones que se ejecutan en una máquina virtual.
Nota: Las máquinas virtuales son similares a las computadoras físicas, pero están integradas en el software. Procesan instrucciones básicas similares al código de ensamblaje .
Python es un lenguaje de programación interpretado. Su código Python se compila utilizando instrucciones que la computadora entiende más fácilmente: el código de
bytes . La máquina virtual interpreta estas instrucciones cuando ejecuta el código.
¿Alguna vez has visto archivos con la extensión
.pyc o la carpeta
__pycache__ ? Este es el mismo código de bytes que interpreta la máquina virtual.
Es importante comprender que hay otras implementaciones además de CPython, por ejemplo
IronPython , que compila y ejecuta en Microsoft Common Language Runtime (CLR).
Jython compila a Java bytecode para ejecutarse en una máquina virtual Java. También hay
PyPy sobre el que puede escribir un artículo separado, por lo que solo lo mencionaré de pasada.
En este artículo, nos enfocaremos en la administración de memoria usando las herramientas de CPython.
Nota: Las versiones de Python se actualizan y cualquier cosa puede suceder en el futuro. Al momento de escribir, la última versión era
Python 3.7 .
Ok, tenemos CPython escrito en C que interpreta el bytecode de Python. ¿Cómo se relaciona esto con la gestión de la memoria? Para comenzar, existen algoritmos y estructuras para administrar la memoria en el código CPython, en C. Para comprender estos principios en Python, necesita una comprensión básica de CPython.
CPython está escrito en C, que a su vez no admite programación orientada a objetos. Debido a esto, el código CPython tiene una estructura bastante interesante.
Debes haber escuchado que todo en Python es un objeto, incluso tipos como int y str, por ejemplo. Esto es cierto en el nivel de implementación de CPython. Hay una estructura llamada PyObject que usa cada objeto en CPython.
Nota: Una estructura en C es un tipo de datos definido por el usuario que agrupa diferentes tipos de datos en sí mismo. Podemos dibujar una analogía con lenguajes orientados a objetos y decir que una estructura es una clase con atributos, pero sin métodos.
PyObject es el progenitor de todos los objetos en Python, que contiene solo dos cosas:
- ob_refcnt : contador de referencia;
- ob_type : puntero a otro tipo.
Se requiere un contador de referencia para la recolección de basura. También tenemos un puntero a un tipo específico de objeto. Un tipo de objeto es solo otra estructura que describe objetos en Python (como dict o int).
Cada objeto tiene un asignador de memoria orientado a objetos que sabe cómo asignar memoria y almacenar el objeto. Cada objeto también tiene un liberador de recursos orientado a objetos que limpia la memoria si sus contenidos ya no son necesarios.
Hay un factor importante al hablar sobre la asignación de memoria y su limpieza. La memoria es un recurso compartido de una computadora, y puede suceder algo desagradable si dos procesos intentan escribir datos en la misma ubicación de memoria al mismo tiempo.
Bloqueo de interpretación global (GIL)GIL es una solución al problema general de compartir memoria entre recursos compartidos como la memoria de la computadora. Cuando dos hilos intentan cambiar el mismo recurso al mismo tiempo, se pisan el uno al otro. Como resultado, se forma un desorden completo en la memoria y ningún proceso finalizará su trabajo con el resultado deseado.
Volviendo a la analogía con el libro, supongamos que de los dos autores, cada uno decide que debe escribir su historia en la página actual en este momento en particular. Cada uno de ellos ignora los intentos del otro de escribir una historia y comienza a escribir tercamente en la página. Como resultado, tenemos dos historias, una encima de la otra, y una página absolutamente ilegible.
Una de las soluciones a este problema es precisamente GIL, que bloquea el intérprete mientras el hilo interactúa con el recurso asignado, permitiendo así que uno y solo un hilo escriba en el área de memoria asignada. Cuando CPython asigna memoria, usa el GIL para asegurarse de que lo hace bien.
Este enfoque tiene muchas ventajas y muchas desventajas, por lo que GIL causa conflictos en la comunidad de Python. Para obtener más información sobre GIL, sugiero leer el siguiente
artículo .
Recolector de basuraVolvamos a nuestra analogía con el libro e imaginemos que algunas historias en él están irremediablemente desactualizadas. Nadie los lee y se dirige a ellos. En este caso, una solución natural sería deshacerse de ellos como innecesarios, liberando así espacio para nuevas historias.
Tales historias antiguas no utilizadas se pueden comparar con objetos en Python cuyo recuento de referencias se ha reducido a 0. Recuerde que cada objeto en Python tiene un recuento de referencias y un puntero a un tipo.
El recuento de referencias puede aumentar por varias razones. Por ejemplo, aumentará si asigna una variable a otra variable.

También aumentará si pasa el objeto como argumento.

En el último ejemplo, el recuento de referencias aumentará si incluye el objeto en la lista.

Python le permite conocer el valor actual del contador de referencia utilizando el módulo sys. Puede usar
sys.getrefcount(numbers)
, pero recuerde que llamar a
getrefcount()
aumentará el recuento de referencia en otro.
En cualquier caso, si el objeto en su código aún es necesario, el valor de su contador de referencia será mayor que 0. Y cuando cae a cero, se iniciará una función especial para borrar la memoria, que la liberará y la pondrá a disposición para otros objetos.
Pero, ¿qué significa "memoria libre" y cómo lo usan otros objetos? Vamos a sumergirnos directamente en la gestión de memoria en CPython.
Gestión de memoria en CPythonEn esta parte, nos sumergiremos en la arquitectura de memoria de CPython y los algoritmos por los que opera.
Como se mencionó anteriormente, existen niveles de abstracción entre el equipo físico y CPython. El sistema operativo (SO) abstrae la memoria física y crea un nivel de memoria virtual al que pueden acceder las aplicaciones, incluido Python.
Un administrador de memoria virtual orientado al sistema operativo asigna un área de memoria específica para los procesos de Python. En la imagen, las áreas gris oscuro son el espacio que ocupa el proceso de Python.

Python usa parte de la memoria para uso interno y memoria sin objetos. La otra parte se divide en el almacenamiento de objetos (su
int, dict , etc.) Ahora hablo en un lenguaje muy simple, sin embargo, puede mirar directamente debajo del capó, es decir, en el
código fuente de CPython y ver cómo sucede todo esto desde un punto de vista práctico. .
En CPython, hay un asignador de objetos responsable de asignar memoria dentro de un área de memoria de objeto. Es en este distribuidor de objetos donde se realiza toda la magia. Se llama cada vez que cada nuevo objeto necesita ocupar o liberar memoria.
Por lo general, agregar y eliminar datos en Python, como int o list, por ejemplo, no usa muchos datos al mismo tiempo. Es por eso que la arquitectura del dispensador se enfoca en trabajar con pequeñas cantidades de datos por unidad de tiempo. Además, no asigna memoria por adelantado, es decir, hasta ese momento hasta que sea absolutamente necesario.
Los comentarios en el código fuente definen al asignador como "un asignador de memoria rápida de propósito especial que funciona como la función universal malloc". En consecuencia, en C, malloc se usa para asignar memoria.
Ahora echemos un vistazo a la estrategia de asignación de memoria de CPython. Primero, hablemos sobre las tres partes principales y cómo se relacionan entre sí.
Las arenas son las áreas más grandes de memoria que ocupan espacio hasta los bordes de las páginas en la memoria. El borde de la página (extensión de página) es el punto extremo de un bloque continuo de memoria de una longitud fija utilizada por el sistema operativo. Python establece el borde de la página del sistema en 256 KB.

Dentro de las arenas hay grupos (grupo), que se consideran una página virtual de memoria (4 Kb). Parecen páginas en nuestra analogía. Las agrupaciones se dividen en piezas de memoria aún más pequeñas: bloques.
Todos los bloques en el grupo se encuentran en una "clase de tamaño". La clase de tamaño determina el tamaño del bloque, que tiene una cierta cantidad de datos solicitados. La gradación en la tabla a continuación se toma directamente de los comentarios en el código fuente:

Por ejemplo, si se necesitan 42 bytes, los datos se colocarán en un bloque de 48 bytes.
PiscinasLas piscinas están formadas por bloques de la misma clase de tamaño. Cada grupo funciona según el principio de una lista doblemente vinculada con otros grupos de la misma clase de tamaño. Por lo tanto, el algoritmo puede encontrar fácilmente el lugar necesario para el tamaño de bloque requerido, incluso entre muchos grupos.
La
usedpools list
realiza un seguimiento de todos los grupos que tienen algún tipo de espacio libre disponible para datos de cada clase de tamaño. Cuando se solicita el tamaño de bloque requerido, el algoritmo verifica la lista de grupos utilizados para encontrar un grupo adecuado para él.
Las piscinas están en tres estados: usado, lleno, vacío. El grupo utilizado contiene bloques en los que se puede escribir cierta información. Todos los bloques del grupo completo están distribuidos y ya contienen datos. Las agrupaciones vacías no contienen datos y pueden desglosarse en qué clases de tamaño son adecuadas si es necesario.
La lista de grupos vacíos (lista de grupos
freepools list
) contiene, respectivamente, todos los grupos en un estado vacío. ¿Pero en qué punto se usan?
Digamos que su código necesita un área de memoria de 8 bytes. Si no hay agrupaciones en la lista de agrupaciones utilizadas con un tamaño de clase de 8 bytes, se inicia una nueva agrupación vacía como bloques de almacenamiento de 8 bytes. Luego, el grupo vacío se agrega a la lista de grupos usados y se puede usar en las siguientes consultas.
Un grupo completo libera algunos bloques cuando ya no se necesita esta información en ellos. Este grupo se agregará a la lista utilizada según su clase de tamaño. Puede observar cómo los grupos cambian sus estados e incluso las clases de tamaño de acuerdo con el algoritmo.
Bloques
Como se puede ver en la figura, los grupos contienen punteros para liberar bloques de memoria. Hay un ligero matiz en su trabajo. Según los comentarios en el código fuente, el distribuidor "se esfuerza por no tocar nunca ningún área de memoria en ninguno de los niveles (arena, grupo, bloque) hasta que sea necesario".
Esto significa que un bloque puede tener tres estados. Se pueden definir de la siguiente manera:
- Sin tocar : áreas de memoria que no han sido asignadas;
- Libre : áreas de memoria que fueron asignadas pero luego liberadas por CPython porque no contenían información relevante;
- Distribuido : áreas de memoria que actualmente contienen información actual.
El puntero de bloque libre es una lista de bloques de memoria libre enlazados. En otras palabras, esta es una lista de lugares gratuitos donde puede escribir información. Si se necesita más memoria de la que hay en los bloques libres, entonces el asignador usa los bloques intactos en el grupo.
Tan pronto como el administrador de memoria libera bloques, estos bloques se agregan a la parte superior de la lista de bloques libres. La lista real puede no contener una secuencia continua de bloques de memoria, como en la primera figura "exitosa".
ArenasLas arenas contienen piscinas. Las arenas, a diferencia de las piscinas, no tienen divisiones estatales explícitas.
Ellos mismos están organizados en una lista doblemente vinculada llamada lista de arenas utilizables (usable_arenas). Esta lista está ordenada por el número de grupos gratuitos. Cuantos menos grupos libres, más cerca está la arena de la parte superior de la lista.

Esto significa que se seleccionará la arena más completa para registrar aún más datos. ¿Pero por qué exactamente? ¿Por qué no escribir datos donde está el espacio más libre?
Esto nos lleva a la idea de liberar completamente la memoria. El hecho es que, en algunos casos, cuando se libera la memoria, aún permanece inaccesible para el sistema operativo. El proceso de Python lo mantiene distribuido y lo usa más tarde para obtener nuevos datos. La desasignación de memoria completa devuelve la memoria al sistema operativo.
Las arenas no son las únicas áreas que pueden desocuparse por completo. Por lo tanto, entendemos que esas arenas que están en la lista de "más cerca del vacío" deben ser liberadas. En este caso, el área de memoria realmente se puede liberar por completo y, en consecuencia, se reduce la capacidad de memoria total de su programa Python.
ConclusiónLa administración de memoria es una de las partes más importantes al trabajar con una computadora. De una forma u otra, Python realiza prácticamente todas sus operaciones en modo sigiloso.
De este artículo aprendiste:
- ¿Qué es la administración de memoria y por qué es importante?
- Qué es CPython, una implementación básica de Python;
- Cómo funcionan los algoritmos y las estructuras de datos en la administración de memoria de CPython y almacena sus datos.
Python resume los muchos matices de trabajar con una computadora. Esto hace posible trabajar a un nivel superior y deshacerse del dolor de cabeza sobre el tema de dónde y cómo se almacenan los bytes de su programa.
Entonces aprendimos sobre la administración de memoria en Python. Tradicionalmente, estamos esperando sus comentarios, y también lo invitamos a una
jornada de puertas abiertas en el curso Python Developer, que tendrá lugar el 13 de marzo.