2009-03-01 10 views
7

Mi programa, por desgracia, tiene una pérdida de memoria en alguna parte, pero estaré condenado si sé de qué se trata.Estrategias para rastrear fugas de memoria cuando has hecho todo lo contrario

Su trabajo es leer en un grupo de ~ 2MB archivos, hacer algunos análisis sintácticos y reemplazo de cadenas, y luego darles salida en varios formatos. Naturalmente, esto significa muchas cuerdas, y al hacerlo, el seguimiento de memoria muestra que tengo muchas cuerdas, que es exactamente lo que esperaría. La estructura del programa es una serie de clases (cada una en su propio hilo, porque soy un idiota) que actúa sobre un objeto que representa cada archivo en la memoria. (Cada objeto tiene una cola de entrada que utiliza un bloqueo en ambos extremos. Si bien esto significa que puedo ejecutar este proceso simple en paralelo, también significa que tengo varios objetos de 2MB en la memoria). La estructura de cada objeto está definida por un objeto de esquema .

Mis clases de procesamiento generan eventos cuando han procesado y pasan una referencia al objeto grande que contiene todas mis cadenas para agregarlo a la cola del siguiente objeto de procesamiento. Reemplazar el evento con una llamada de función para agregar a la cola no detiene la fuga. Uno de los formatos de salida requiere que use un objeto no administrado. La implementación de Dispose() en la clase no detiene la fuga. He reemplazado todas las referencias al objeto de esquema con un nombre de índice. No dados. No tengo idea de qué lo está causando, y no tengo idea de dónde mirar. La traza de memoria no ayuda porque todo lo que veo es un conjunto de cadenas que se crean, y no veo dónde se quedan las referencias en la memoria.

Estamos a punto de darnos por vencidos y retroceder en este punto, pero tengo una necesidad patológica de saber exactamente cómo me equivoqué. Sé que Stack Overflow no puede peinar exactamente mi código, pero ¿qué estrategias puede sugerir para rastrear esta fuga? Probablemente voy a hacer esto en mi propio tiempo, por lo que cualquier enfoque es viable.

Respuesta

11

Una técnica que intentaría es reducir sistemáticamente la cantidad de código que necesita para demostrar el problema sin que el problema desaparezca. Esto se conoce informalmente como "divide y vencerás" y es una poderosa técnica de depuración. Una vez que tenga un ejemplo small que demuestre el mismo problema, será mucho más fácil para usted entenderlo. Quizás el problema de memoria se aclarará en ese punto.

+0

+1 Llamo a esto "búsqueda binaria", porque deshabilito la mitad del código y pruebo para ver si el problema todavía existe. Repita con la otra mitad. Suponiendo que obtenga resultados consistentes, ahora puedo deshabilitar la mitad del medio que contiene el problema, y ​​así sucesivamente hasta que haya aislado la causa. –

5

Solo hay una persona que puede ayudarlo. El nombre de esa persona es Tess Ferrandez. (Silencio silencioso)

Pero en serio. lee su blog (el primer artículo es bastante pertinente). Ver cómo depura estas cosas le dará una gran perspectiva para saber qué está pasando con su problema.

+0

+2 (si pudiera). Enlace a la entrada más relevante: http://blogs.msdn.com/tess/archive/2009/02/27/net-memory-leak-reader-email-are-you-really-leaking-net-memory.aspx – Richard

2

Me gusta el CLR Profiler de Microsoft. Proporciona algunas herramientas excelentes para visualizar el montón administrado y rastrear fugas.

0
  1. Agregar código al constructor del objeto unamanaged para iniciar sesión cuando es onstructed, y ordenar un identificador único. Use esa identificación única cuando el objeto se destruye de nuevo, y puede al menos decir cuáles van por mal camino.
  2. Codifique el código para cada lugar donde construya un objeto nuevo; siga esa ruta de código para ver si tiene que coincida con destroy.
  3. Agregue punteros de encadenamiento a los objetos construidos , por lo que tiene un enlace al objeto construido antes y después del actual. Luego puedes barrerlos más tarde.
  4. Agregue los contadores de referencia.
  5. ¿Hay un "debug malloc" disponible?
0

La depuración logró poner en SoS (Hijo de huelga) es inmensamente poweful para el seguimiento de 'fugas' abajo administrados de memoria, ya que son, por definición visible desde las raíces GC.

va a trabajar en WinDbg o Visual Studio (a pesar de que en muchos aspectos es más fácil de usar en WinDbg)

No es en absoluto fácil llegar a controlar. Aquí está un tutorial

Me gustaría secundar la recomendación de ver también el blog de Tess Fernandez.

0

Utilizo el generador de perfiles dotTrace para rastrear las pérdidas de memoria. Es mucho más determinista que la prueba y el error metodológicos y muestra los resultados mucho más rápido.

Para cualquier acción que realice el sistema, tomo una instantánea y luego ejecuto algunas iteraciones de la función, luego tomo otra instantánea. Al comparar los dos, se mostrarán todos los objetos que se crearon en el medio pero no se liberaron. Luego puede ver el marco de pila en el punto de su creación y, por lo tanto, determinar qué instancias no se liberan.

0

¿Cómo sabe que tiene una fuga de memoria?

Otra cosa: Usted escribe que sus clases de procesamiento están usando eventos. Si ha registrado un controlador de eventos, mantendrá vivo el objeto que posee el evento, es decir, el GC no podrá recopilarlo. Asegúrese de anular el registro de todos los controladores de eventos si desea que sus objetos sean recolectados.

+0

Espero hasta que el programa esté usando medio gigabyte de memoria. Entonces estoy bastante seguro. – Merus

+0

Esto es un poco frívolo, entonces: hay un escenario en el que puedo ejecutarlo para que esté leyendo en datos, analizándolo y leyéndolo, y está usando solo unos 50MB. Esto esta bien. Si agrego otras etapas, globos de uso de memoria. – Merus

1

sale esto: http://www.red-gate.com/Products/ants_profiler/index.htm

La memoria y perfiles de rendimiento son impresionantes. Ser capaz de ver los números correctos en lugar de adivinar hace que la optimización sea bastante rápida. Lo he usado bastante para reducir la memoria de nuestra aplicación principal.

+0

Intentó HORMIGAS y fue terrible. Redujo el rendimiento a paso de tortuga, lo que hizo aún más difícil seguir lo que estaba sucediendo. – Merus

+0

Cada generador de perfiles que he probado ha disminuido el rendimiento. Pasarás mucho tiempo buscando posibles respuestas a menos que uses una herramienta, y es probable que pases por alto algo simple. –

0

Si el objeto no administrado realmente es la causa de la fuga, es posible que desee tener que llamar AddMemoryPressure cuando se asigna memoria no administrado y RemoveMemoryPressure en Finalize/Dispose/donde quiera que desasigna la memoria no administrada. Esto le dará al GC un mejor manejo de la situación, porque de lo contrario no se dará cuenta de que es necesario programar la recopilación.

+0

estrictamente hablando, es menos probable que active una recolección de basura. Hay una observación sobre la presión de memoria adicional que hace que el objeto sea un objetivo para GC específicamente. – ShuggyCoUk

+0

No dude en editar la respuesta para que sea correcta sin citarme. No me ofenderé y hace que la respuesta sea mucho más legible. Siempre puedo eliminar el comentario si se sale de lugar ... – ShuggyCoUk

+0

Solo trato de dar crédito donde se debe crédito. Pero acepto que es más legible de lo contrario. –

0

Tenga cuidado con la definición de "fuga". "Utiliza más memoria" o incluso "usa demasiada memoria" no es lo mismo que "pérdida de memoria". Esto es especialmente cierto en un entorno recolectado de basura. Puede ser simplemente que GC no haya necesitado recolectar la memoria extra que está viendo utilizada. También tenga cuidado con la diferencia entre el uso de la memoria virtual y el uso de la memoria física.

Finalmente, no todas las "fugas de memoria" son causadas por problemas de "memoria". Una vez me dijeron (no se me pidió) que solucionara una fuga de memoria urgente que causaba que IIS se reiniciara con frecuencia. De hecho, hice un perfil y descubrí que estaba usando muchas cadenas a través de la clase StringBuilder. Implementé un conjunto de objetos (desde un artículo de MSDN) para StringBuilders, y el uso de la memoria disminuyó sustancialmente.

IIS todavía se reinicia con la misma frecuencia. Esto fue porque no hubo pérdida de memoria. En cambio, había un código no administrado que decía ser seguro para subprocesos, pero no lo era.Su uso en un servicio web (varios hilos) hizo que escribiera en todo el montón de C Runtime Library. Dado que nadie estaba buscando excepciones no administradas, nadie vio esto hasta que hice un perfil con AQtime de QA automatizado. Resulta que tiene una ventana de eventos, que pasó a mostrar los gritos de dolor de la Biblioteca de C Runtime.

Colocado bloquea las llamadas al código no administrado y la "pérdida de memoria" desapareció.

0

Mencionaste que estás usando eventos. ¿Estás eliminando los controladores de esos eventos cuando terminaste con tu objeto? Descubrí que los controladores de eventos 'sueltos' causarán muchos problemas de pérdida de memoria si agrega varios controladores sin eliminarlos cuando haya terminado.

0

La mejor herramienta de perfiles de memoria para .Net es la siguiente:

http://memprofiler.com

Además, mientras esté aquí, el mejor rendimiento de perfiles para .Net es la siguiente:

http://www.yourkit.com/dotnet/download/index.jsp

También tienen una gran relación calidad-precio, tienen un bajo costo y son fáciles de usar. Cualquier persona seria acerca del desarrollo de .Net debería considerar ambas como una inversión personal y comprar de inmediato. Ambos tienen una prueba gratuita.

Trabajo en un motor de juego en tiempo real con más de 700k líneas de código escritas en C# y he pasado cientos de horas usando ambas herramientas. ¡He usado el producto Sci Tech desde 2002 y YourKit! durante los últimos tres años. Aunque he intentado con algunos de los otros, siempre he vuelto a estos.

En mi humilde opinión, son absolutamente geniales.

0

similares a Charlie Martin, se puede hacer algo como esto:

static unigned __int64 _foo_id = 0; 
foo::foo() 
{ 
    ++_foo_id; 
    if (_foo_id == MAGIC_BAD_ALLOC_ID) 
     DebugBreak(); 
    std::werr << L"foo::foo @ " << _foo_id << std::endl; 
} 
foo::~foo() 
{ 
    --_foo_id; 
    std::werr << L"foo::~foo @ " << _foo_id << std::endl; 
} 

Si puede volver a crearlo, aunque sea una vez o dos veces con el mismo ID de asignación, esto le permitirá ver lo que está sucediendo en ese momento y allí (obviamente TLS/threading tiene que ser manejado también, si es necesario, pero lo dejé fuera para mayor claridad).