Asignación de memoria de amigos
La técnica de asignación de memoria de amigos es un algoritmo de asignación de memoria que divide la memoria en particiones para intentar satisfacer una solicitud de memoria de la manera más adecuada posible. Este sistema utiliza la división de la memoria en mitades para intentar lograr un mejor ajuste. Según Donald Knuth, el sistema de amigos fue inventado en 1963 por Harry Markowitz y descrito por primera vez por Kenneth C. Knowlton (publicado en 1965). La asignación de memoria de Buddy es relativamente fácil de implementar. Admite una división y fusión limitada pero eficiente de bloques de memoria.
Algoritmo
Hay varias formas del sistema de compañeros; aquellas en las que cada bloque se subdivide en dos bloques más pequeños son la variedad más sencilla y común. Cada bloque de memoria en este sistema tiene un orden, donde el orden es un número entero que va desde 0 hasta un límite superior específico. El tamaño de un bloque de orden n es proporcional a 2n, de modo que los bloques tienen exactamente el doble de tamaño que los bloques de un orden inferior. Los tamaños de bloques de potencia de dos simplifican el cálculo de direcciones, porque todos los compañeros están alineados en límites de direcciones de memoria que son potencias de dos. Cuando se divide un bloque más grande, se divide en dos bloques más pequeños, y cada bloque más pequeño se convierte en un compañero único para el otro. Un bloque dividido solo se puede fusionar con su bloque amigo único, que luego reforma el bloque más grande del que se separó.
Para empezar, se determina el tamaño del bloque más pequeño posible, es decir, el bloque de memoria más pequeño que se puede asignar. Si no existiera ningún límite inferior (por ejemplo, fueran posibles asignaciones de tamaño de bits), habría mucha memoria y sobrecarga computacional para que el sistema realizara un seguimiento de qué partes de la memoria están asignadas y no asignadas. Sin embargo, puede ser deseable un límite bastante bajo, de modo que se minimice el desperdicio promedio de memoria por asignación (con respecto a asignaciones que, en tamaño, no son múltiplos del bloque más pequeño). Normalmente, el límite inferior sería lo suficientemente pequeño como para minimizar el espacio promedio desperdiciado por asignación, pero lo suficientemente grande como para evitar una sobrecarga excesiva. El tamaño de bloque más pequeño se toma entonces como el tamaño de un bloque de orden 0, de modo que todos los órdenes superiores se expresan como potencias de dos múltiplos de este tamaño.
El programador luego tiene que decidir, o escribir código para obtener, el orden más alto posible que pueda caber en el espacio de memoria disponible restante. Dado que la memoria total disponible en un sistema informático determinado puede no ser un múltiplo de una potencia de dos del tamaño mínimo de bloque, es posible que el tamaño de bloque más grande no abarque toda la memoria del sistema. Por ejemplo, si el sistema tuviera 2000 K de memoria física y el tamaño del bloque de orden 0 fuera 4 K, el límite superior del orden sería 8, ya que un bloque de orden 8 (256 bloques de orden 0, 1024 K) es el bloque más grande que cabe en la memoria. En consecuencia, es imposible asignar toda la memoria física en un solo fragmento; los 976 K restantes de memoria tendrían que asignarse en bloques más pequeños.
Ejemplo
El siguiente es un ejemplo de lo que sucede cuando un programa realiza solicitudes de memoria. Supongamos que en este sistema, el bloque más pequeño posible tiene un tamaño de 64 kilobytes y el límite superior para el orden es 4, lo que da como resultado el bloque asignable más grande posible, 24 multiplicado por 64 K = 1024 K. en tamaño. A continuación se muestra un posible estado del sistema después de varias solicitudes de memoria.
| Paso | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K | 64 K |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 24 | |||||||||||||||
| 2.1 | 23 | 23 | ||||||||||||||
| 2.2 | 22 | 22 | 23 | |||||||||||||
| 2.3 | 21 | 21 | 22 | 23 | ||||||||||||
| 2.4 | 20 | 20 | 21 | 22 | 23 | |||||||||||
| 2.5 | A: 20 | 20 | 21 | 22 | 23 | |||||||||||
| 3 | A: 20 | 20 | B: 21 | 22 | 23 | |||||||||||
| 4 | A: 20 | C: 20 | B: 21 | 22 | 23 | |||||||||||
| 5.1 | A: 20 | C: 20 | B: 21 | 21 | 21 | 23 | ||||||||||
| 5.2 | A: 20 | C: 20 | B: 21 | D: 21 | 21 | 23 | ||||||||||
| 6 | A: 20 | C: 20 | 21 | D: 21 | 21 | 23 | ||||||||||
| 7.1 | A: 20 | C: 20 | 21 | 21 | 21 | 23 | ||||||||||
| 7.2 | A: 20 | C: 20 | 21 | 22 | 23 | |||||||||||
| 8 | 20 | C: 20 | 21 | 22 | 23 | |||||||||||
| 9.1 | 20 | 20 | 21 | 22 | 23 | |||||||||||
| 9.2 | 21 | 21 | 22 | 23 | ||||||||||||
| 9.3 | 22 | 22 | 23 | |||||||||||||
| 9.4 | 23 | 23 | ||||||||||||||
| 9.5 | 24 | |||||||||||||||
Esta asignación podría haber ocurrido de la siguiente manera
- La situación inicial.
- Programa A pide memoria 34 K, orden 0.
- No hay orden 0 bloques disponibles, por lo que un orden 4 bloque se divide, creando dos orden 3 bloques.
- Todavía no hay orden 0 bloques disponibles, por lo que el primer orden 3 bloques se divide, creando dos orden 2 bloques.
- Aún sin orden 0 bloques disponibles, por lo que el primer orden 2 bloques se divide, creando dos orden 1 bloques.
- Aún sin orden 0 bloques disponibles, por lo que el primer pedido 1 bloque se divide, creando dos orden 0 bloques.
- Ahora hay un orden 0 bloque disponible, por lo que se asigna a A.
- Programa B pide memoria 66 K, orden 1. Un pedido 1 bloque está disponible, por lo que se asigna a B.
- Programa C pide memoria 35 K, orden 0. Un orden 0 bloque está disponible, por lo que se asigna a C.
- Programa D pide memoria 67 K, orden 1.
- No hay orden 1 bloques disponibles, por lo que un orden 2 bloques se divide, creando dos orden 1 bloques.
- Ahora hay un pedido 1 bloque disponible, por lo que se asigna a D.
- Programa B libera su memoria, liberando un pedido 1 bloque.
- El programa D libera su memoria.
- Una orden 1 bloque es liberado.
- Dado que el bloque amigo del nuevo bloque liberado también es libre, los dos se fusionan en un solo orden de 2 bloques.
- Programa Una libera su memoria, liberando un pedido 0 bloque.
- Programa C libera su memoria.
- Una orden 0 bloque es liberado.
- Dado que el bloque amigo del nuevo bloque liberado también es libre, los dos se fusionan en un solo orden 1 bloque.
- Dado que el bloque amigo del nuevo pedido formado 1 bloque también es libre, los dos se fusionan en un solo orden 2 bloque.
- Dado que el bloque amigo del nuevo pedido 2 bloque es también libre, los dos se fusionan en un solo orden 3 bloque.
- Dado que el bloque amigo del nuevo pedido 3 bloque es también libre, los dos se fusionan en un solo orden 4 bloque.
Como puedes ver, lo que sucede cuando se realiza una solicitud de memoria es lo siguiente:
- Si se desea asignar la memoria
- Busque una ranura de memoria de un tamaño adecuado (el mínimo 2k bloque que es mayor o igual al de la memoria solicitada)
- Si se encuentra, se asigna al programa
- Si no, intenta hacer una ranura de memoria adecuada. El sistema lo hace al intentar lo siguiente:
- Dividir una ranura de memoria libre más grande que el tamaño de memoria solicitado en la mitad
- Si se alcanza el límite inferior, entonces asigne esa cantidad de memoria
- Volver al paso 1 (mirar una ranura de memoria de un tamaño adecuado)
- Repita este proceso hasta que se encuentre una ranura de memoria adecuada
- Si la memoria es ser liberada
- Libre el bloque de memoria
- Mira el bloque vecino - ¿Es gratis también?
- Si lo es, combinar los dos, y volver al paso 2 y repetir este proceso hasta que se alcance el límite superior (toda la memoria se libera), o hasta que se encuentre un bloque vecino no libre
Implementación y eficiencia
En comparación con otras técnicas más simples, como la asignación dinámica, el sistema de memoria de amigos tiene poca fragmentación externa y permite la compactación de la memoria con poca sobrecarga. El método de compañero para liberar memoria es rápido, con el número máximo de compactaciones requeridas igual a O(orden más alto) = O(log2(tamaño total de la memoria)). Normalmente, el sistema de asignación de memoria de amigos se implementa con el uso de un árbol binario para representar bloques de memoria dividida usados o no utilizados. La dirección del "amigo" de un bloque; es igual al OR exclusivo bit a bit (XOR) de la dirección del bloque y el tamaño del bloque.
Sin embargo, todavía existe el problema de la fragmentación interna: la memoria se desperdicia porque la memoria solicitada es un poco más grande que un bloque pequeño, pero mucho más pequeña que un bloque grande. Debido a la forma en que funciona la técnica de asignación de memoria de amigos, a un programa que solicita 66 K de memoria se le asignarían 128 K, lo que resulta en un desperdicio de 62 K de memoria. Este problema se puede resolver mediante la asignación de bloques, que se puede colocar en capas encima del asignador de compañeros más aproximado para proporcionar una asignación más detallada.
Donald Knuth describió en detalle una versión del algoritmo de asignación de amigos en el volumen 1 de El arte de la programación informática. El kernel de Linux también utiliza el sistema de amigos, con modificaciones adicionales para minimizar la fragmentación externa, junto con varios otros asignadores para administrar la memoria dentro de los bloques.
jemalloc es un asignador de memoria moderno que emplea, entre otras, la técnica del amigo.