Cómo resolver la fragmentación de la memoria

Ocasionalmente, hemos tenido problemas por los cuales nuestros procesos de servidor de larga ejecución (que se ejecutan en Windows Server 2003) arrojaron una excepción debido a un error de asignación de memoria. Nuestra sospecha es que estas asignaciones están fallando debido a la fragmentación de la memoria.

Por lo tanto, hemos estado analizando algunos mecanismos alternativos de asignación de memoria que pueden ayudarnos y espero que alguien pueda decirme cuál es el mejor:

1) Utilice el montón de baja fragmentación de Windows

2) jemalloc – como se usa en Firefox 3

3) Malloc de Doug Lea

Nuestro proceso de servidor se desarrolla utilizando código C ++ multiplataforma, por lo que cualquier solución sería ideal también multiplataforma (¿los sistemas operativos do * nix sufren este tipo de fragmentación de memoria?).

Además, ¿tengo razón al pensar que LFH es ahora el mecanismo predeterminado de asignación de memoria para Windows Server 2008 / Vista? … ¿Mis problemas actuales “desaparecerán” si nuestros clientes simplemente actualizan su sistema operativo?

Primero, estoy de acuerdo con los otros carteles que sugirieron una fuga de recursos. Realmente quieres descartar eso primero.

Afortunadamente, el administrador de stack que está utilizando actualmente tiene una manera de volcar el espacio libre total disponible en el montón (en todos los bloques libres ) y también el número total de bloques que está dividido. Si el tamaño de bloque libre promedio es relativamente pequeño en comparación con el espacio libre total en el montón, entonces tiene un problema de fragmentación. Alternativamente, si puede volcar el tamaño del bloque libre más grande y compararlo con el espacio libre total, eso logrará lo mismo. El bloque libre más grande sería pequeño en relación con el espacio libre total disponible en todos los bloques si se está ejecutando en la fragmentación.

Para ser muy claros sobre lo anterior, en todos los casos estamos hablando de bloques libres en el montón, no de bloques asignados en el montón. En cualquier caso, si las condiciones anteriores no se cumplen, entonces tiene una situación de fuga de algún tipo.

Entonces, una vez que haya descartado una fuga, podría considerar usar un mejor asignador. El malloc de Doug Lea sugerido en la pregunta es un muy buen asignador para aplicaciones de uso general y muy robusto la mayor parte del tiempo. Dicho de otra manera, se ha comprobado que funciona muy bien para la mayoría de las aplicaciones. Sin embargo, ningún algoritmo es ideal para todas las aplicaciones y cualquier enfoque de algoritmo de gestión se puede romper con las condiciones patológicas adecuadas frente a su diseño.

¿Por qué tienes un problema de fragmentación? – Las fonts de los problemas de fragmentación son causadas por el comportamiento de una aplicación y tienen que ver con vidas de asignación muy diferentes en el mismo ámbito de memoria. Es decir, algunos objetos se asignan y liberan regularmente, mientras que otros tipos de objetos persisten durante largos períodos de tiempo, todos en el mismo montón … piense en los más largos de por vida como agujeros en áreas más grandes de la arena y, por lo tanto, evitando fusión de bloques adyacentes que han sido liberados.

Para abordar este tipo de problema, lo mejor que puede hacer es dividir lógicamente el montón en subáreas donde las vidas son más similares. En efecto, desea un montón transitorio y un montón persistente o montones que agrupen cosas de vidas similares.

Algunos otros han sugerido otro enfoque para resolver el problema que es tratar de hacer que los tamaños de asignación sean más similares o idénticos, pero esto es menos ideal porque crea un tipo diferente de fragmentación llamada fragmentación interna, que en realidad es el espacio desperdiciado que tiene asignando más memoria en el bloque de la que necesita.

Además, con un buen repartidor de montones, como el de Doug Lea, hacer que los tamaños de bloque sean más similares es innecesario porque el asignador ya estará haciendo un esquema de dos niveles de tamaño que hará que sea completamente innecesario ajustar artificialmente los tamaños de asignación pasados ​​a malloc ( ) – en efecto, su administrador de montón lo hace automáticamente mucho más fuerte de lo que la aplicación podrá hacer ajustes.

Creo que has descartado por error una fuga de memoria demasiado temprano. Incluso una pequeña pérdida de memoria puede causar una fragmentación de memoria grave.

Suponiendo que su aplicación se comporta de la siguiente manera:
Asigne 10MB
Asignar 1 byte
Gratis 10MB
(Uy, no liberamos el byte 1, pero a quién le importa un byte diminuto)

Esto parece una fuga muy pequeña, apenas lo notará cuando monitoree solo el tamaño total de la memoria asignada . Pero esta fuga eventualmente hará que la memoria de su aplicación se vea así:
.
.
Gratis – 10 MB
.
.
[Asignado -1 byte]
.
.
Gratis – 10 MB
.
.
[Asignado -1 byte]
.
.
Gratis – 10 MB
.
.

Esta fuga no se notará … hasta que desee asignar 11 MB
Suponiendo que sus minivolcados tenían información de memoria completa incluida, le recomiendo usar DebugDiag para detectar posibles fugas. En el informe de memoria generada, examine cuidadosamente el conteo de asignación (no el tamaño) .

Como sugiere, el malloc de Doug Lea podría funcionar bien. Es multiplataforma y se ha utilizado en el código de envío. Por lo menos, debería ser fácil de integrar en su código para la prueba.

Habiendo trabajado en entornos de memoria fija durante varios años, esta situación es ciertamente un problema, incluso en entornos no fijos. Hemos encontrado que los asignadores de CRT tienden a apestar bastante mal en términos de rendimiento (velocidad, eficiencia del espacio desperdiciado, etc.). Creo firmemente que si tienes una gran necesidad de un buen asignador de memoria durante un largo período de tiempo, deberías escribir el tuyo (o ver si algo como dlmalloc funcionará). El truco es obtener algo escrito que funcione con sus patrones de asignación, y eso tiene más que ver con la eficiencia de administración de memoria que con casi cualquier otra cosa.

Prueba dlmalloc. Definitivamente le doy un pulgar hacia arriba. También es bastante optimizable, por lo que es posible que pueda obtener más eficiencia al cambiar algunas de las opciones de tiempo de comstackción.

Honestamente, no debe depender de que las cosas “se vayan” con las nuevas implementaciones del sistema operativo. Un service pack, parche u otro nuevo sistema operativo N años más tarde podría empeorar el problema. Nuevamente, para las aplicaciones que exigen un administrador de memoria robusto, no use las versiones disponibles que están disponibles con su comstackdor. Encuentre uno que funcione para su situación. Comience con dlmalloc y sintonícelo para ver si puede obtener el comportamiento que mejor se adapte a su situación.

Puede ayudar a reducir la fragmentación al reducir la cantidad que asigna desasignar.

por ejemplo, para un servidor web que ejecuta un script del lado del servidor, puede crear una cadena para mostrar la página. En lugar de asignar y desasignar estas cadenas para cada solicitud de página, simplemente mantén un grupo de ellas, de modo que solo asignes cuando necesites más, pero no desasignas (lo que significa que después de un tiempo obtienes la situación que ya no asignas, porque tienes suficiente)

Puede usar _CrtDumpMemoryLeaks (); para descargar memory leaks a la ventana de depuración cuando se ejecuta una comstackción de depuración, sin embargo, creo que esto es específico del comstackdor de Visual C. (está en crtdbg.h)

Sospecharía una fuga antes de sospechar la fragmentación.

Para las estructuras de datos intensivas en memoria, puede cambiar a un mecanismo de grupo de almacenamiento reutilizable. También es posible que pueda asignar más cosas en la stack que en el montón, pero en términos prácticos eso no hará una gran diferencia, creo.

Arrancaría una herramienta como valgrind o realizaría un registro intensivo para buscar recursos que no se lanzan.

@nsaners: estoy bastante seguro de que el problema se debe a la fragmentación de la memoria. Hemos analizado minivolcados que apuntan a un problema cuando se asigna un gran trozo de memoria (5-10 mb). También hemos supervisado el proceso (en el sitio y en desarrollo) para verificar si hay pérdidas de memoria: no se detectó ninguno (la huella de memoria es generalmente bastante baja).

El problema ocurre en Unix, aunque generalmente no es tan malo.

El montón Low-framgmentation nos ayudó, pero mis compañeros de trabajo juran por Smart Heap (se ha utilizado multiplataforma en algunos de nuestros productos durante años). Desafortunadamente, debido a otras circunstancias, no pudimos usar Smart Heap esta vez.

También analizamos la asignación de bloque / fragmentación y tratamos de tener pools / estrategias con amplio scope, es decir, cosas a largo plazo aquí, todo lo que se solicita allí, cosas a corto plazo, etc.

Como de costumbre, generalmente puede perder memoria para ganar velocidad.

Esta técnica no es útil para un asignador de propósito general, pero sí tiene su lugar.

Básicamente, la idea es escribir un asignador que devuelva la memoria de un grupo donde todas las asignaciones son del mismo tamaño. Este conjunto nunca puede fragmentarse porque cualquier bloque es tan bueno como otro. Puede reducir el desperdicio de memoria creando múltiples grupos con diferentes tamaños de tamaño y seleccionando el grupo de tamaño de porción más pequeño que sea aún mayor que la cantidad solicitada. He usado esta idea para crear asignadores que se ejecutan en O (1).

si habla de Win32, puede intentar exprimir algo utilizando LARGEADDRESSAWARE. Tendrás ~ 1Gb de memoria desfragmentada adicional para que tu aplicación la fragmente por más tiempo.