Gesti贸n de memoria Python

驴Alguna vez te has preguntado c贸mo se ven los datos con los que trabajas en las entra帽as de Python? 驴Sobre c贸mo se crean y almacenan las variables en la memoria? 驴C贸mo y cu谩ndo se eliminan? El material, cuya traducci贸n publicamos, est谩 dedicado a la investigaci贸n de las profundidades de Python, durante el cual trataremos de descubrir las caracter铆sticas de la administraci贸n de memoria en este lenguaje. Despu茅s de estudiar este art铆culo, comprender谩 c贸mo funcionan los mecanismos de bajo nivel de las computadoras, especialmente los relacionados con la memoria. Comprender谩 c贸mo Python abstrae las operaciones de bajo nivel y c贸mo administra la memoria.



Saber lo que est谩 sucediendo en Python le permitir谩 comprender mejor algunos de los comportamientos de este lenguaje. Esto, espero, le dar谩 la oportunidad de apreciar el enorme trabajo que se est谩 realizando dentro de la implementaci贸n de este lenguaje que utiliza para que sus programas funcionen exactamente como lo necesita.

La memoria es un libro vacio


La memoria de la computadora, al comienzo de trabajar con ella, se puede representar en forma de un libro vac铆o destinado a cuentos. Si bien no hay nada en sus p谩ginas, pronto aparecer谩n los autores de las historias, cada uno de los cuales quiere escribir su propia historia en este libro.

Como una historia no se puede escribir sobre otra, los autores deben tener cuidado con las p谩ginas del libro en las que escriben. Antes de escribir nada, consultan con el editor en jefe. 脡l decide d贸nde exactamente los autores pueden grabar historias.

Dado que el libro del que hablamos ha existido durante bastante tiempo, muchas de las historias ya est谩n desactualizadas. Si nadie lee una historia o la menciona en sus obras, esta historia se elimina del libro, dejando espacio para nuevas historias.

En general, podemos decir que la memoria de la computadora es muy similar a un libro de este tipo. De hecho, los bloques continuos de memoria de una longitud fija incluso se llaman p谩ginas, por lo que creemos que comparar la memoria con un libro es muy exitoso.

Los autores que escriben sus historias en un libro son diferentes aplicaciones o procesos que necesitan almacenar datos en la memoria. El editor jefe, que decide qu茅 p谩ginas del libro pueden escribir los autores, es el mecanismo que se ocupa de la gesti贸n de la memoria. Y el que elimina viejas historias del libro, dejando espacio para nuevas, se puede comparar con el mecanismo de recolecci贸n de basura.

Gesti贸n de la memoria: el camino del hierro a los programas


La gesti贸n de la memoria es un proceso, durante la implementaci贸n de los cuales los programas escriben datos en la memoria y los leen. Un administrador de memoria es una entidad que determina d贸nde exactamente una aplicaci贸n puede colocar sus datos en la memoria. Dado que el n煤mero de fragmentos de memoria que se pueden asignar a las aplicaciones no es infinito, as铆 como el n煤mero de p谩ginas en cualquier libro no es infinito, el administrador de memoria, que sirve a las aplicaciones, necesita encontrar fragmentos de memoria libres y proporcionarlos a las aplicaciones. Este proceso, en el que la memoria se asigna a las aplicaciones, se denomina asignaci贸n de memoria.

Por otro lado, cuando algunos datos ya no son necesarios, se pueden eliminar o, en otras palabras, liberar la memoria que ocupa. Pero, 驴qu茅 es exactamente "aislar" y "liberar" cuando se habla de memoria?

En alg煤n lugar de su computadora hay un dispositivo f铆sico que almacena los datos utilizados por los programas Python mientras funcionan. Antes de que un objeto Python aparezca en la memoria f铆sica, el c贸digo debe pasar por muchas capas de abstracci贸n.

Una de las principales capas de este tipo, que se encuentra en la parte superior del hardware (como RAM o disco duro) es el sistema operativo (SO). Ejecuta (o se niega a cumplir) solicitudes para leer datos de la memoria y escribir datos en la memoria.

Encima del sistema operativo hay una aplicaci贸n, en nuestro caso, una de las implementaciones de Python (puede ser un paquete de software que es parte de su sistema operativo o descargado de python.org ). Es este paquete de software el que se dedica a la gesti贸n de la memoria, lo que garantiza el funcionamiento de su c贸digo Python. El enfoque de este art铆culo est谩 en los algoritmos y las estructuras de datos que Python usa para administrar la memoria.

Implementaci贸n de referencia de Python


La implementaci贸n de Python de referencia se llama CPython. Est谩 escrito en C. Cuando lo escuch茅 por primera vez, literalmente me inquiet贸. 驴Un lenguaje de programaci贸n que est谩 escrito en otro idioma? Bueno, en realidad, esto no es del todo cierto.

La especificaci贸n de Python se describe en ingl茅s simple en este documento . Sin embargo, esta especificaci贸n sola, el c贸digo escrito en Python, por supuesto, no puede ejecutarse. Para hacer esto, necesita algo que, siguiendo las reglas de esta especificaci贸n, pueda interpretar el c贸digo escrito en Python.

Adem谩s, necesita algo que pueda ejecutar el c贸digo interpretado en la computadora. La implementaci贸n de Python de referencia resuelve ambas tareas. Convierte el c贸digo en instrucciones que luego se ejecutan en la m谩quina virtual.

Las m谩quinas virtuales son similares a las computadoras comunes hechas de silicio, metal y otros materiales, pero est谩n implementadas en software. Por lo general, est谩n ocupados procesando instrucciones b谩sicas, similares a las instrucciones escritas en Assembler .

Python es un lenguaje interpretado. El c贸digo escrito en Python se compila en un conjunto de instrucciones que es conveniente para la computadora, en el llamado c贸digo de bytes . La m谩quina virtual interpreta estas instrucciones cuando ejecuta su programa.

驴Alguna vez has visto archivos con la extensi贸n .pyc o la carpeta __pycache__ ? Contienen el mismo c贸digo de bytes interpretado por la m谩quina virtual.

Es importante tener en cuenta que, adem谩s de CPython, hay otras implementaciones de Python. Por ejemplo, cuando se usa IronPython, el c贸digo Python se compila en una declaraci贸n CLR de Microsoft. En Jython, el c贸digo se compila en c贸digo de bytes Java y se ejecuta en una m谩quina virtual Java. En el mundo de Python, existe algo as铆 como PyPy , pero es digno de un art铆culo separado, as铆 que aqu铆 solo lo mencionamos.

Para los fines de este art铆culo, me enfocar茅 en c贸mo funcionan los mecanismos de administraci贸n de memoria en la implementaci贸n de referencia de Python: CPython.

Cabe se帽alar que aunque la mayor parte de lo que vamos a hablar aqu铆 ser谩 cierto para las nuevas versiones de Python, las cosas pueden cambiar en el futuro. Por lo tanto, preste atenci贸n al hecho de que en este art铆culo me centro en la 煤ltima versi贸n de Python al momento de escribir: Python 3.7 .

Entonces, el paquete de software CPython est谩 escrito en C, interpreta el c贸digo de bytes de Python. 驴Qu茅 tiene esto que ver con la gesti贸n de la memoria? El hecho es que los algoritmos y las estructuras de datos utilizados para la gesti贸n de la memoria existen en el c贸digo CPython escrito, como ya se ha dicho, en C. Para comprender c贸mo funciona la gesti贸n de la memoria en Python, primero debe comprender un poco sobre CPython.

El lenguaje C en el que se escribe CPython no tiene soporte incorporado para la programaci贸n orientada a objetos. Debido a esto, se utilizan muchas soluciones arquitect贸nicas interesantes en el c贸digo CPython.

Es posible que haya escuchado que todo en Python es un objeto, incluso los tipos de datos primitivos como int y str . Y este es el caso en el nivel de implementaci贸n del lenguaje en CPython. Hay una estructura llamada PyObject , que es utilizada por los objetos creados en CPython.

Una estructura es un tipo de datos compuesto que puede agrupar datos de diferentes tipos. Si compara esto con la programaci贸n orientada a objetos, la estructura es similar a una clase que tiene atributos pero no m茅todos.

PyObject es el antepasado de todos los objetos de Python. Esta estructura contiene solo dos campos:

  • ob_refcnt - contador de referencia.
  • ob_type : puntero a otro tipo.

El contador de referencia se usa para implementar el mecanismo de recolecci贸n de basura. Otro campo de PyObject es un puntero a un tipo espec铆fico de objeto. Este tipo est谩 representado por otra estructura que describe el objeto Python (por ejemplo, puede ser un tipo dict o int ).

Cada objeto tiene su propio mecanismo de asignaci贸n de memoria, 煤nico para dicho objeto, que sabe c贸mo obtener la memoria necesaria para almacenar este objeto. Adem谩s, cada objeto tiene su propio mecanismo para liberar memoria, que "libera" la memoria despu茅s de que ya no es necesaria.

Sin embargo, debe tenerse en cuenta que en todas estas conversaciones sobre la asignaci贸n y liberaci贸n de memoria, hay un factor importante. El hecho es que la memoria de la computadora es un recurso compartido. Si, al mismo tiempo, dos procesos diferentes intentan escribir algo en la misma 谩rea de memoria, puede suceder algo malo.

Int茅rprete Global Lock


Global Interpreter Lock (GIL) es una soluci贸n a un problema com煤n que ocurre cuando se trabaja con recursos inform谩ticos compartidos como la memoria. Cuando dos hilos intentan modificar simult谩neamente el mismo recurso, pueden "colisionar" entre s铆. El resultado ser谩 un desastre y ninguna de las transmisiones lograr谩 lo que buscaba.

Volvamos a la analog铆a del libro nuevamente. Imagine que dos autores decidieron arbitrariamente que ahora les tocaba tomar notas. Pero tambi茅n decidieron tomar notas simult谩neamente en la misma p谩gina.

Cada uno de ellos no presta atenci贸n al hecho de que el otro est谩 tratando de escribir su historia. Juntos comienzan a escribir texto en la p谩gina. Como resultado, se grabar谩n dos historias all铆, una encima de la otra, lo que har谩 que la p谩gina sea completamente ilegible.

Una de las soluciones a este problema es un 煤nico mecanismo de int茅rprete global que bloquea los recursos compartidos con los que est谩 trabajando un determinado hilo. En nuestro ejemplo, este es un "mecanismo" que "bloquea" la p谩gina de un libro. Tal mecanismo elimina la situaci贸n descrita anteriormente, en la que dos autores escriben simult谩neamente texto en la misma p谩gina.

El mecanismo GIL en Python logra esto mediante el bloqueo de todo el int茅rprete. Como resultado, nada puede interferir con el funcionamiento del hilo actual. Y cuando CPython est谩 trabajando con memoria, utiliza el GIL para garantizar que este trabajo se realice de manera segura y eficiente.

Hay puntos fuertes y d茅biles en este enfoque, y el GIL es objeto de un feroz debate en la comunidad de Python. Para obtener m谩s informaci贸n sobre GIL, puede echar un vistazo a este material .

Recolecci贸n de basura


Volvamos a la analog铆a del libro e imaginemos que algunas de las historias registradas en este libro est谩n irremediablemente desactualizadas. Nadie los lee, nadie los menciona en ning煤n lado. Y si nadie lee o hace referencia a alg煤n material en sus obras, entonces este material puede eliminarse, dejando espacio para nuevos textos.

Estos viejos cuentos olvidados se pueden comparar con los objetos de Python cuyos recuentos de referencia son cero. Estos son los mismos contadores de los que hablamos cuando discutimos la estructura de PyObject .

El contador de enlaces se incrementa por varias razones. Por ejemplo, el contador se incrementa si el objeto almacenado en una variable se escribe en otra variable:

 numbers = [1, 2, 3] #   = 1 more_numbers = numbers #   = 2 

Aumenta cuando el objeto se pasa a alguna funci贸n como argumento:

 total = sum(numbers) 

Y aqu铆 hay otro ejemplo de una situaci贸n en la que el n煤mero en el contador de referencia aumenta. Esto sucede si el objeto est谩 incluido en la lista:

 matrix = [numbers, numbers, numbers] 

Python permite al programador encontrar el valor actual del recuento de referencia de un determinado objeto utilizando el m贸dulo sys . Para esto, se utiliza la siguiente construcci贸n:

 sys.getrefcount(numbers) 

getfefcount() , debe recordar que pasar un objeto al m茅todo getfefcount() aumenta el valor del contador en 1.

En cualquier caso, si el objeto todav铆a se usa en alg煤n lugar del c贸digo, su contador de referencia ser谩 mayor que 0. Cuando el valor del contador cae a 0, entrar谩 en juego una funci贸n especial que "libera" la memoria ocupada por el objeto. Esta memoria puede ser utilizada por otros objetos.

Ahora nos hacemos preguntas sobre qu茅 es "liberar memoria" y sobre c贸mo otros objetos pueden usar esta memoria. Para responder a estas preguntas, hablemos sobre los mecanismos de administraci贸n de memoria en CPython.

Mecanismos de gesti贸n de memoria en CPython


Ahora hablaremos sobre c贸mo CPython tiene una arquitectura de memoria y c贸mo se realiza la administraci贸n de memoria all铆.

Como ya se mencion贸, hay varias capas de abstracci贸n entre CPython y la memoria f铆sica. El sistema operativo abstrae la memoria f铆sica y crea una capa de memoria virtual con la que las aplicaciones pueden trabajar (esto tambi茅n se aplica a Python).

El administrador de memoria virtual de un sistema operativo espec铆fico asigna un trozo de memoria para el proceso de Python. Las 谩reas de color gris oscuro en la siguiente imagen son las piezas de memoria que pertenecen al proceso de Python.


脕reas de memoria utilizadas por CPython

Python usa una cierta cantidad de memoria para uso interno y para necesidades no relacionadas con la asignaci贸n de memoria para objetos. Se usa otra pieza de memoria para almacenar objetos (estos son valores de los tipos int , dict y otros similares). Tenga en cuenta que este es un diagrama simplificado. Si desea ver la imagen completa, eche un vistazo al c贸digo fuente de CPython , donde est谩 sucediendo todo lo que estamos hablando.

CPython tiene una facilidad para asignar memoria para objetos, que es responsable de asignar memoria en el 谩rea destinada para almacenar objetos. Lo m谩s interesante sucede cuando este mecanismo funciona. Se llama cuando el objeto necesita memoria, o en los casos en que la memoria necesita ser liberada.

Por lo general, agregar o eliminar datos a objetos de Python como list e int no implica el procesamiento simult谩neo de grandes cantidades de informaci贸n. Por lo tanto, la arquitectura de la herramienta de asignaci贸n de memoria se construye teniendo en cuenta el procesamiento de peque帽as cantidades de datos. Adem谩s, esta herramienta busca no asignar memoria hasta que quede claro que es absolutamente necesario.

Los comentarios en el c贸digo fuente describen la herramienta de asignaci贸n de memoria como "una herramienta de asignaci贸n de memoria r谩pida y especializada para bloques peque帽os que est谩 dise帽ada para usarse encima del malloc universal". En este caso, malloc es una funci贸n de biblioteca C dise帽ada para asignar memoria.

Analicemos la estrategia de asignaci贸n de memoria utilizada por CPython. Primero, hablaremos de tres entidades: los llamados bloques (bloques), piscinas (piscinas) y arenas (arena), y c贸mo se relacionan entre s铆.

Las arenas son los fragmentos m谩s grandes de memoria. Est谩n alineados en los bordes de las p谩ginas de la memoria. El l铆mite de la p谩gina es donde el bloque continuo de memoria de longitud fija termina en uso por el sistema operativo. Python, mientras trabaja con memoria, supone que el tama帽o de la p谩gina de memoria del sistema es de 256 KB.


Arenas, Piscinas y Bloques

Los grupos se encuentran en las arenas, que son p谩ginas de memoria virtual de 4 KB. Se parecen a las p谩ginas del libro de nuestro ejemplo. Las piscinas se dividen en peque帽os bloques de memoria.

Todos los bloques en el mismo grupo pertenecen a la misma clase de tama帽o. La clase de tama帽o a la que pertenece el bloque determina el tama帽o de este bloque, que se selecciona teniendo en cuenta el tama帽o de memoria solicitado. Aqu铆 hay una tabla tomada del c贸digo fuente que muestra la cantidad de datos que el sistema solicita almacenar en la memoria, los tama帽os de los bloques asignados y los identificadores de las clases de tama帽o.
La cantidad de datos en bytes.
Tama帽o del bloque
tama帽o de clase idx
1-8
8
0 0
9-16
16
1
17-24
24
2
25-32
32
3
33-40
40
4 4
41-48
48
5 5
49-56
56
6 6
57-64
64
7 7
65-72
72
8
...
...
...
497-504
504
62
505-512
512
63

Por ejemplo, si se solicita que se almacenen 42 bytes, los datos se colocar谩n en un bloque de 48 bytes.

Piscinas


Las agrupaciones consisten en bloques que pertenecen a la misma clase de tama帽o. Cada grupo est谩 asociado con otros grupos que contienen bloques de la misma clase de tama帽o utilizando el mecanismo de lista doblemente vinculada. Con este enfoque, el algoritmo de asignaci贸n de memoria puede encontrar f谩cilmente espacio libre para un bloque de un tama帽o determinado, incluso si se trata de encontrar espacio libre en diferentes grupos.

La lista de usedpools permite realizar un seguimiento de todos los pools en los que hay espacio para datos que pertenecen a una clase de tama帽o particular. Cuando se le solicita guardar un bloque de cierto tama帽o, el algoritmo verifica esta lista para ver una lista de grupos que almacenan bloques del tama帽o requerido.

Las piscinas mismas deben estar en uno de los tres estados. Es decir, se pueden usar (estado used ), se pueden llenar ( full ) o vac铆as ( empty ). El grupo utilizado tiene bloques libres en los que es posible guardar datos de un tama帽o adecuado. Todos los bloques del grupo lleno se asignan para datos. Un grupo vac铆o no contiene datos y, si es necesario, puede asignarse a bloques de tiendas que pertenezcan a cualquier clase de tama帽o.

La lista de freepools almacena informaci贸n sobre todos los grupos que est谩n en estado empty . Por ejemplo, si no hay entradas en la lista de grupos usedpools sobre grupos que almacenan bloques de 8 bytes (clase con idx 0), entonces se inicializa un nuevo grupo, que est谩 en estado empty , dise帽ado para almacenar dichos bloques. Este nuevo grupo se agrega a la lista de usedpools , se puede usar para cumplir con las solicitudes para guardar los datos recibidos despu茅s de su creaci贸n.

Suponga que en un grupo que est谩 en estado full , algunos bloques se liberan. Esto se debe al hecho de que los datos almacenados en ellos ya no son necesarios. Este grupo volver谩 a estar en la lista de usedpools y se puede usar para datos de la clase de tama帽o correspondiente.

Conocer este algoritmo nos permite comprender c贸mo cambia el estado de los grupos durante la operaci贸n (y c贸mo cambian las clases de tama帽o, los bloques que pertenecen pueden almacenarse en ellos).

Bloques



Piscinas usadas, llenas y vac铆as

Como puede ver en la ilustraci贸n anterior, los grupos contienen punteros a los bloques de memoria "libres" que contienen. En cuanto al trabajo con bloques, se debe tener en cuenta una peque帽a caracter铆stica, que se indica en el c贸digo fuente. El sistema de gesti贸n de memoria utilizado en CPython, en todos los niveles (arenas, agrupaciones, bloques), se esfuerza por asignar memoria solo cuando es absolutamente necesario.

Esto significa que los grupos pueden contener bloques que se encuentran en uno de los tres estados:

  • untouched es la porci贸n de memoria que a煤n no se ha asignado.
  • free : la parte de la memoria que ya estaba asignada, pero que luego fue "libre" por CPython y ya no contiene datos valiosos.
  • allocated es la porci贸n de memoria que contiene datos valiosos.

El puntero de freeblock apunta a una lista individualmente vinculada de bloques de memoria libre. En otras palabras, esta es una lista de lugares donde puede colocar datos. Si se necesita m谩s de un bloque libre para colocar datos, entonces la herramienta de asignaci贸n de memoria tomar谩 varios bloques del grupo que est谩n en estado untouched .

A medida que la herramienta de administraci贸n de memoria hace que los bloques sean "libres", ellos, cuando adquieren el estado free , llegan a la parte superior de la lista de freeblock . Los bloques contenidos en esta lista no representan necesariamente una regi贸n de memoria contigua similar a la que se muestra en la figura anterior. En realidad, pueden parecerse a la de abajo.


Lista de bloque libre individual vinculada

Arenas


Las arenas contienen piscinas. Estas agrupaciones, como ya se mencion贸, pueden residir en los estados used , full o empty . Cabe se帽alar que las arenas no tienen estados similares a los que tienen las piscinas.

Las arenas se organizan en una lista doblemente vinculada llamada usable_arenas . Esta lista est谩 ordenada por la cantidad de agrupaciones gratuitas disponibles. Cuantas menos piscinas libres haya en la arena, m谩s cerca estar谩 la arena de la parte superior de la lista.


Lista de arenas utilizables

Esto significa que la arena, que es m谩s fuerte que otras llenas de datos, se seleccionar谩 para colocar nuevos datos en ella. 驴Y por qu茅 no al rev茅s? 驴Por qu茅 no publicar nuevos datos en la arena con el mayor espacio libre?

De hecho, esta caracter铆stica nos lleva a la idea de liberar realmente la memoria. Es posible que haya notado que a menudo usamos el concepto de "liberar memoria" aqu铆, encerr谩ndolo entre comillas. La raz贸n por la que se hizo esto es que, aunque el bloque puede considerarse "libre", la pieza de memoria que representa no se devuelve al sistema operativo. El proceso de Python contiene este fragmento de memoria y luego lo usa para almacenar nuevos datos. La verdadera liberaci贸n de memoria es el retorno de su sistema operativo, que puede aprovecharlo.

Las arenas son la 煤nica entidad en el esquema considerado aqu铆, la memoria representada por la cual puede ser verdaderamente liberada. El sentido com煤n dicta que el esquema descrito anteriormente de trabajar con arenas tiene como objetivo permitir que aquellas arenas que est谩n casi vac铆as se vacian por completo. Con este enfoque, esa pieza de memoria que est谩 representada por una arena completamente vac铆a puede liberarse realmente, lo que reducir谩 la cantidad de memoria consumida por Python.

Resumen


Esto es lo que aprendiste al leer este material:

  • 驴Qu茅 es la administraci贸n de memoria y por qu茅 es importante?
  • C贸mo se organiza la implementaci贸n de referencia de Python, Cpython, escrita en el lenguaje de programaci贸n C.
  • Qu茅 estructuras de datos y algoritmos se utilizan en CPython para la gesti贸n de la memoria.

La gesti贸n de la memoria es una parte integral del trabajo de los programas de computadora. Python resuelve casi todas las tareas de administraci贸n de memoria sin que el programador las note. Python permite que cualquiera que escriba en este idioma ignore los muchos peque帽os detalles relacionados con el trabajo con computadoras. Esto le da al programador la oportunidad de trabajar a un nivel superior, para crear su propio c贸digo sin preocuparse de d贸nde se almacenan sus datos.

Estimados lectores! Si tiene experiencia con el desarrollo de Python, d铆ganos c贸mo aborda el uso de la memoria en sus programas. Por ejemplo, 驴buscas guardarlo?

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


All Articles