¿Qué es la fragmentación de la memoria?

Resuelto AshleysBrain asked hace 13 años • 12 respuestas

He escuchado el término "fragmentación de memoria" usado varias veces en el contexto de la asignación de memoria dinámica de C++. Encontré algunas preguntas sobre cómo lidiar con la fragmentación de la memoria, pero no puedo encontrar una pregunta directa que la aborde en sí. Entonces:

  • ¿Qué es la fragmentación de la memoria?
  • ¿Cómo puedo saber si la fragmentación de la memoria es un problema para mi aplicación? ¿Qué tipo de programa tiene más probabilidades de verse afectado?
  • ¿Cuáles son buenas formas comunes de lidiar con la fragmentación de la memoria?

También:

  • He oído que el uso frecuente de asignaciones dinámicas puede aumentar la fragmentación de la memoria. ¿Es esto cierto? En el contexto de C++, entiendo que todos los contenedores estándar (std::string, std::vector, etc.) usan asignación de memoria dinámica. Si se utilizan en todo un programa (especialmente std::string), ¿es más probable que la fragmentación de la memoria sea un problema?
  • ¿Cómo se puede abordar la fragmentación de la memoria en una aplicación con mucho STL?
AshleysBrain avatar Sep 22 '10 21:09 AshleysBrain
Aceptado

Imagine que tiene una "gran" extensión de memoria libre (32 bytes):

----------------------------------
|                                |
----------------------------------

Ahora, asigne una parte (5 asignaciones):

----------------------------------
|aaaabbccccccddeeee              |
----------------------------------

Ahora, libera las primeras cuatro asignaciones pero no la quinta:

----------------------------------
|              eeee              |
----------------------------------

Ahora, intenta asignar 16 bytes. Vaya, no puedo, aunque hay casi el doble de dinero gratis.

En sistemas con memoria virtual, la fragmentación es un problema menor de lo que podría pensar, porque las asignaciones grandes sólo necesitan ser contiguas en el espacio de direcciones virtuales , no en el espacio de direcciones físicas . Entonces, en mi ejemplo, si tuviera memoria virtual con un tamaño de página de 2 bytes, podría realizar mi asignación de 16 bytes sin problemas. La memoria física quedaría así:

----------------------------------
|ffffffffffffffeeeeff            |
----------------------------------

mientras que la memoria virtual (que es mucho más grande) podría verse así:

------------------------------------------------------...
|              eeeeffffffffffffffff                   
------------------------------------------------------...

El síntoma clásico de la fragmentación de la memoria es que intentas asignar un bloque grande y no puedes, aunque parezca que tienes suficiente memoria libre. Otra posible consecuencia es la incapacidad del proceso para liberar memoria al sistema operativo (porque a cada uno de los bloques grandes que ha asignado desde el sistema operativo, mallocetc. para subdividir, le queda algo, aunque la mayor parte de cada bloque ya no se utiliza).

Las tácticas para evitar la fragmentación de la memoria en C++ funcionan asignando objetos de diferentes áreas según su tamaño y/o su vida útil esperada. Entonces, si vas a crear muchos objetos y destruirlos todos juntos más tarde, asígnalos desde un grupo de memoria. Cualquier otra asignación que realice entre ellos no será del grupo, por lo tanto, no se ubicará entre ellos en la memoria, por lo que la memoria no se fragmentará como resultado. O, si va a asignar muchos objetos del mismo tamaño, asígnelos del mismo grupo. Entonces, un tramo de espacio libre en el grupo nunca puede ser más pequeño que el tamaño que intenta asignar de ese grupo.

Generalmente no necesita preocuparse mucho por eso, a menos que su programa sea de larga duración y realice muchas asignaciones y liberaciones. Es cuando tienes mezclas de objetos de vida corta y larga cuando corres mayor riesgo, pero incluso entonces mallochará todo lo posible para ayudar. Básicamente, ignórelo hasta que su programa tenga fallas de asignación o inesperadamente haga que el sistema se quede sin memoria (¡observe esto durante las pruebas, de preferencia!).

Las bibliotecas estándar no son peores que cualquier otra cosa que asigne memoria, y todos los contenedores estándar tienen un Allocparámetro de plantilla que puede usar para ajustar su estrategia de asignación si es absolutamente necesario.

Steve Jessop avatar Sep 22 '2010 15:09 Steve Jessop

¿Qué es la fragmentación de la memoria?

La fragmentación de la memoria ocurre cuando la mayor parte de la memoria se asigna en una gran cantidad de bloques o fragmentos no contiguos, lo que deja un buen porcentaje de la memoria total sin asignar, pero inutilizable para la mayoría de los escenarios típicos. Esto da como resultado excepciones de falta de memoria o errores de asignación (es decir, malloc devuelve nulo).

La forma más sencilla de pensar en esto es imaginar que tienes una gran pared vacía en la que necesitas colocar cuadros de diferentes tamaños . Cada imagen ocupa un tamaño determinado y obviamente no puedes dividirla en partes más pequeñas para que encaje. Necesitas un lugar vacío en la pared, del tamaño del cuadro, o no podrás colocarlo. Ahora, si empiezas a colgar cuadros en la pared y no tienes cuidado con cómo los organizas, pronto terminarás con una pared parcialmente cubierta de cuadros y, aunque tengas espacios vacíos, la mayoría de los cuadros nuevos no encajarán. porque son más grandes que los lugares disponibles. Aún puedes colgar cuadros muy pequeños, pero la mayoría no caben. Así que tendrás que reorganizar (compactar) los que ya están en la pared para dejar espacio para más.

Ahora, imagina que la pared es tu memoria (montón) y las imágenes son objetos. Eso es fragmentación de la memoria.

¿Cómo puedo saber si la fragmentación de la memoria es un problema para mi aplicación? ¿Qué tipo de programa tiene más probabilidades de verse afectado?

Una señal reveladora de que puede estar lidiando con fragmentación de la memoria es si recibe muchos errores de asignación, especialmente cuando el porcentaje de memoria utilizada es alto, pero aún no ha usado toda la memoria, por lo que técnicamente debería tener suficiente espacio. para los objetos que está intentando asignar.

Cuando la memoria está muy fragmentada, las asignaciones de memoria probablemente tardarán más porque el asignador de memoria tiene que hacer más trabajo para encontrar un espacio adecuado para el nuevo objeto. Si a su vez tiene muchas asignaciones de memoria (lo que probablemente tenga ya que terminó con una fragmentación de la memoria), el tiempo de asignación puede incluso causar retrasos notables.

¿Cuáles son buenas formas comunes de lidiar con la fragmentación de la memoria?

Utilice un buen algoritmo para asignar memoria. En lugar de asignar memoria para muchos objetos pequeños, asigne previamente memoria para una matriz contigua de esos objetos más pequeños. A veces, desperdiciar un poco la memoria al asignarla puede mejorar el rendimiento y ahorrarle la molestia de tener que lidiar con la fragmentación de la memoria.

Mike Dinescu avatar Sep 22 '2010 15:09 Mike Dinescu

La fragmentación de la memoria es el mismo concepto que la fragmentación del disco: se refiere al espacio que se desperdicia porque las áreas en uso no están lo suficientemente juntas.

Supongamos, para un ejemplo sencillo de juguete, que tienes diez bytes de memoria:

 |   |   |   |   |   |   |   |   |   |   |
   0   1   2   3   4   5   6   7   8   9

Ahora asignemos tres bloques de tres bytes, llamados A, B y C:

 | A | A | A | B | B | B | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

Ahora desasignar el bloque B:

 | A | A | A |   |   |   | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

Ahora, ¿qué sucede si intentamos asignar un bloque D de cuatro bytes? Bueno, tenemos cuatro bytes de memoria libres, pero no tenemos cuatro bytes de memoria contiguos libres, por lo que no podemos asignar D. Este es un uso ineficiente de la memoria, porque deberíamos haber podido almacenar D, pero no pudimos. Y no podemos mover C para hacer espacio, porque es muy probable que algunas variables de nuestro programa apunten a C y no podemos encontrar ni cambiar automáticamente todos estos valores.

¿Cómo sabes que es un problema? Bueno, la señal más importante es que el tamaño de la memoria virtual de tu programa es considerablemente mayor que la cantidad de memoria que realmente estás usando. En un ejemplo del mundo real, tendría más de diez bytes de memoria, por lo que D simplemente se asignaría comenzando con el byte 9, y los bytes 3-5 permanecerían sin usar a menos que luego asignara algo de tres bytes de largo o menos.

En este ejemplo, 3 bytes no es mucho que desperdiciar, pero considere un caso más patológico en el que dos asignaciones de un par de bytes están, por ejemplo, a diez megabytes de distancia en la memoria, y necesita asignar un bloque de tamaño 10 megabytes. + 1 byte. Tienes que pedirle al sistema operativo más de diez megabytes más de memoria virtual para hacer eso, aunque ya te falta un byte para tener suficiente espacio.

¿Cómo lo previenes? Los peores casos tienden a surgir cuando creas y destruyes objetos pequeños con frecuencia, ya que eso tiende a producir un efecto de "queso suizo" con muchos objetos pequeños separados por muchos agujeros pequeños, lo que hace imposible asignar objetos más grandes en esos agujeros. Cuando sepa que va a hacer esto, una estrategia eficaz es preasignar un bloque grande de memoria como grupo para los objetos pequeños y luego gestionar manualmente la creación de los objetos pequeños dentro de ese bloque, en lugar de dejar que el asignador predeterminado lo maneja.

En general, cuantas menos asignaciones haga, es menos probable que la memoria se fragmente. Sin embargo, STL se ocupa de esto con bastante eficacia. Si tiene una cadena que utiliza la totalidad de su asignación actual y le agrega un carácter, no simplemente se reasigna a su longitud actual más uno, sino que duplica su longitud. Esta es una variación de la estrategia de "grupo para asignaciones pequeñas y frecuentes". La cadena está tomando una gran cantidad de memoria para que pueda manejar de manera eficiente pequeños aumentos de tamaño repetidos sin realizar pequeñas reasignaciones repetidas. De hecho, todos los contenedores STL hacen este tipo de cosas, por lo que generalmente no necesitará preocuparse demasiado por la fragmentación causada por la reasignación automática de contenedores STL.

Aunque, por supuesto, los contenedores STL no agrupan memoria entre sí, por lo que si va a crear muchos contenedores pequeños (en lugar de unos pocos contenedores que cambian de tamaño con frecuencia), es posible que deba preocuparse por evitar la fragmentación de la misma manera que lo hace. lo haría para cualquier objeto pequeño creado con frecuencia, STL o no.

Tyler McHenry avatar Sep 22 '2010 15:09 Tyler McHenry