2009-11-05 16 views
9

Tengo problemas con la fragmentación de memoria en mi programa y no puedo asignar bloques de memoria muy grandes después de un tiempo. He leído las publicaciones relacionadas en este foro, principalmente this. Y todavía tengo algunas preguntas.División de montón y administrador de memoria de Windows

He estado utilizando un espacio de memoria profiler para obtener una imagen de la memoria. Escribí un programa de 1 línea que contiene cin >> var; y tomó una foto de la memoria:

alt text http://img22.imageshack.us/img22/6808/memoryk.gif Donde en el arco superior - verde indica el espacio vacío, amarillo asignado, rojo comprometido. Mi pregunta es ¿qué es esa memoria asignada a la derecha? ¿Es la pila para el hilo principal? Esta memoria no va a ser liberada y divide la memoria continua que necesito. En este sencillo programa de 1 línea, la división no es tan mala. Mi programa real tiene más cosas asignadas justo en el medio del espacio de direcciones, y no sé de dónde viene. No estoy asignando ese recuerdo todavía.

  1. ¿Cómo puedo tratar de resolver esto? Estaba pensando en cambiar a algo así como nedmalloc o dlmalloc. Sin embargo, eso solo se aplicaría a los objetos que yo mismo asignase explícitamente, mientras que la división mostrada en la imagen no desaparecería. ¿O hay una forma de reemplazar la asignación de CRT con otro administrador de memoria?

  2. Hablando de objetos, ¿hay envoltorios para nedmalloc para C++ entonces puedo usar nuevo y eliminar para asignar objetos?

Thanks.

+1

Microsoft Security Essentials cree que la aplicación "profiler" vinculada en la pregunta original contiene el troyano Win32.Bisar! Rts. –

Respuesta

12

Primero, gracias por usar mi herramienta. Espero que lo encuentres útil y siéntate libre de enviar solicitudes de funciones o contribuciones.

Normalmente, las divisiones delgadas en puntos fijos en el espacio de direcciones son causadas por dlls vinculados que cargan en su dirección preferida. Los que se cargan arriba en el espacio de direcciones tienden a ser dlls del sistema operativo de Microsoft. Es más eficiente para el sistema operativo si todos pueden cargarse en sus direcciones preferidas porque entonces las partes de solo lectura de las DLL se pueden compartir entre procesos.

La porción que puede ver no es nada de qué preocuparse, apenas elimina algo de su espacio de direcciones. Como ya habrás notado, hay dlls que se cargan en otros puntos del espacio de direcciones. IIRC shlwapi.dll es un ejemplo particularmente malo, cargando a aproximadamente 0x2000000 (nuevamente IIRC) que a menudo divide una gran parte del espacio de direcciones disponible en dos piezas más pequeñas. El problema con esto es que una vez que se carga el archivo DLL, no hay nada que pueda hacer para mover este espacio asignado.

Si enlaza con la DLL (ya sea directamente o a través de otra DLL), no hay nada que pueda hacer. Si usa LoadLibrary, puede ser astuto y reservar su dirección preferida, forzándola a reubicarse, con frecuencia en algún lugar mejor en el espacio de direcciones, antes de liberar esa memoria reservada. Sin embargo, esto no siempre funciona.

Bajo el capó, Monitor de espacio de direcciones utiliza VirtualQueryEx para examinar el espacio de direcciones del proceso, pero hay otra llamada de la biblioteca PSAPI que utilizan otras herramientas (por ejemplo Process Explorer) que le puede mostrar qué archivos (incluyendo archivos DLL) se asignan en qué partes del espacio de direcciones.

Como has encontrado, puede ser increíblemente fácil quedarse sin espacio en un espacio de direcciones de usuario de 2 GB. Fundamentalmente, tu mejor defensa contra la fragmentación de la memoria es simplemente no requerir grandes bloques contiguos de memoria. Aunque es difícil adaptarlo, el diseño de su aplicación para trabajar con fragmentos de tamaño mediano generalmente hace un uso sustancialmente más eficiente del espacio de direcciones.

De forma similar, puede utilizar una estrategia de paginación, posiblemente utilizando archivos mapeados en memoria o Address Windowing Extensions.

+0

Hola y gracias por una gran herramienta, y señalando esa característica de Process Explorer. – Budric

+0

@Charles Bailey: re enlace estático: ¿no se podría volver a establecer una DLL? – rpg

+0

Sí, puede volver a establecer la base de archivos DLL, y esto * puede * ayudar a optimizar la fragmentación del espacio de direcciones y tiempos de carga, sin embargo ... normalmente solo debe hacer esto con DLL que posee y si hace demasiadas micro-optimizaciones terminará con un conjunto de DLL que funcionan bien en un exe en particular en un punto en el tiempo, pero para cualquier otro exe no tienen una estrategia de carga tan buena y optimizada. Si sus archivos DLL se reconstruyen y cambian de tamaño, debe realizar el proceso nuevamente. Por lo tanto, puede funcionar, hasta cierto punto, pero si tiene que recurrir a él puede terminar en un círculo vicioso de alto mantenimiento. –

1

¿Podría ser el ejecutable? Tiene que ser cargado en el espacio de direcciones en alguna parte ...

En cuanto a 2, es bastante fácil anular las funciones globales nuevas y eliminar ... simplemente defínalas.

2

Supongo que con frecuencia está asignando y desasignando objetos de diferentes tamaños y que esto es lo que conduce a los problemas de fragmentación de la memoria.

Existen varias estrategias para evitar esto; los diferentes administradores de memoria que mencionaste podrían ayudarte si pueden resolver el problema de la fragmentación por ti, pero eso requeriría un poco más de análisis de las causas subyacentes de la fragmentación. Por ejemplo, si con frecuencia asigna objetos de tres o cuatro tipos y estos tienden a empeorar el problema de fragmentación de la memoria, puede colocarlos en sus propios grupos de memoria para permitir la reutilización de los bloques de memoria del tamaño correcto. De esta forma, debe tener un conjunto de bloques de memoria disponibles que se ajusten a este objeto particular y evitar el escenario común de que la asignación del objeto X divida un bloque de memoria lo suficientemente grande como para contener Y de tal forma que repentinamente no pueda asignar ningún Ys nunca más.

En cuanto a (2), no conozco un envoltorio alrededor de nedmalloc (francamente, no estoy muy familiarizado con nedmalloc) pero puedes crear tus propios contenedores muy fácilmente ya que puedes crear operadores específicos de clase nuevo y eliminar o incluso sobrecargar/reemplazar los operadores globales nuevos y eliminar. No soy un gran admirador de este último, pero si su "punto de acceso" de asignación consiste en un puñado de clases, por lo general es bastante fácil actualizarlas con sus propios operadores específicos de clase nuevos y eliminar.

Dicho esto, nedmalloc se anuncia como un reemplazo para el estándar malloc/free y al menos con los compiladores de MS, creo que la biblioteca de tiempo de ejecución de C++ reenviará/eliminará a malloc/free así que podría ser un caso de construir tu ejecutable con nedmalloc.

1

La mejor manera de averiguar dónde se asigna la memoria en su programa es utilizar un depurador. Hay asignaciones para cada archivo DLL cargado y el archivo ejecutable, y todos ellos fragmentan la memoria virtual. Además, el uso de bibliotecas C/C++ y API de Windows hará que se cree un montón en su aplicación, que al menos reservará una porción de memoria virtual.

Podría, por ejemplo, usar VirtualAlloc para reservar una gran cantidad de memoria virtual en un programa relativamente pequeño, solo para descubrir que VirtualAlloc falla o que la aplicación falla más tarde cuando intenta cargar una nueva DLL (etc.) Tampoco siempre se puede controlar qué archivos DLL se cargarán y dónde. Muchos A/V y otros productos inyectarán archivos DLL en todos los procesos en ejecución a medida que se inicien. Cuando esto sucede, esos archivos DLL a menudo tienen primera selección en las direcciones de carga, es decir, es probable que se otorguen sus compilados/vinculados por defecto. Fuera del espacio de direcciones virtuales de 2 GB disponible de una aplicación típica de Windows de 32 bits, si una DLL se carga justo en el medio de ese espacio de direcciones, la asignación/reserva individual más grande que puede adquirir será de menos de 1 GB.

Si usa windbg, puede ver qué regiones de la memoria se consumen, están reservadas, etc. El comando lm le mostrará las direcciones de carga de todas las DLL y el EXE y su rango. El comando! Vadump le mostrará toda la memoria virtual utilizada por el proceso y las protecciones de la página. Las protecciones de página son una gran pista sobre lo que hay allí. Por ejemplo, en el siguiente vaduo (parcial)! De un proceso calc.exe de 64 bits, verá que la primera región es simplemente un rango de memoria virtual protegida contra el acceso. (Entre otras cosas, esto evita que se asigne memoria a la dirección 0.) MEM_COMMIT significa que la memoria está respaldada por la RAM o el archivo de paginación. PAGE_READWRITE es posiblemente una memoria de montón, o el segmento de datos de un módulo cargado. PAGE_READEXECUTE es generalmente un código que se carga y que aparecerá en la lista producida por lm. MEM_RESERVE significa algo ha llamado VirtualAlloc para reservar una región de la memoria, pero que no está asignado por el administrador de la memoria virtual, y así sucesivamente ...

0:004> !vadump 
BaseAddress:  0000000000000000 
RegionSize:  0000000000010000 
State:    00010000 MEM_FREE 
Protect:   00000001 PAGE_NOACCESS 

BaseAddress:  0000000000010000 
RegionSize:  0000000000010000 
State:    00001000 MEM_COMMIT 
Protect:   00000004 PAGE_READWRITE 
Type:    00040000 MEM_MAPPED 

BaseAddress:  0000000000020000 
RegionSize:  0000000000003000 
State:    00001000 MEM_COMMIT 
Protect:   00000002 PAGE_READONLY 
Type:    00040000 MEM_MAPPED 

espero que ayuda a explicar las cosas. Windbg es una gran herramienta y tiene muchas extensiones para ayudarte a encontrar dónde se usa la memoria.

Si realmente te importa el montón, mira! Montón.

+0

Gracias, probaré windbg. – Budric

2

Para reducir la fragmentación de la memoria, puede aprovechar el Windows Low-Fragmentation Heap. Hemos utilizado esto en nuestro producto con buenos resultados y no hemos tenido casi tantos problemas relacionados con la memoria desde que lo hicimos.

+0

He visto esta característica. Sin embargo, para habilitarlo, debe ejecutar HeapSetInformation(). La instantánea de la memoria se tomó directamente en la primera línea de main() y la memoria ya está fragmentada después de los primeros 1,3 GB de espacio de direcciones. Después de mirarlo en el explorador de proceso, son DLL y otras cosas. Entonces LFH puede ayudar, pero no previene la fragmentación que ya está ocurriendo debido a la carga de DLL. – Budric

Cuestiones relacionadas