Gestión de memoria Python

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

  1. La memoria es un libro vacio
  2. Gestión de memoria: del hardware al software
  3. Implementación base de Python
  4. Concepto de bloqueo de intérprete global (GIL)
  5. Recolector de basura
  6. Gestión de memoria en CPython:
    • Piscinas
    • Bloques
    • Arenas
  7. 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 vacio

Puedes 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 software

La 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 Python

La 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 basura

Volvamos 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 CPython

En 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.

Piscinas

Las 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".



Arenas

Las 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ón

La 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.

Source: https://habr.com/ru/post/443312/


All Articles