Pregunta

Mi programa, por desgracia, tiene una pérdida de memoria en algún lugar, pero me condenarán si sé lo que es.

Su trabajo es leer un grupo de ~ 2 MB de archivos, hacer un análisis y reemplazar cadenas, y luego generarlos en varios formatos. Naturalmente, esto significa muchas cadenas y, por lo tanto, hacer un seguimiento de la memoria muestra que tengo muchas cadenas, que es exactamente lo que esperaba. 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 sencillo procesamiento en paralelo, también significa que tengo varios objetos de 2 MB en la memoria). La estructura de cada objeto está definida por un objeto de esquema. .

Mis clases de procesamiento generan eventos cuando han terminado su procesamiento 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. Implementar 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, ni idea de dónde mirar. La traza de memoria no ayuda porque todo lo que veo es un conjunto de cadenas que se están creando, y no veo dónde se guardan las referencias en la memoria.

Ya casi nos rendiremos y retrocederemos en este punto, pero tengo una necesidad patológica de saber exactamente cómo arruiné esto. Sé que Stack Overflow no puede peinar exactamente mi código, pero ¿qué estrategias puede sugerir para rastrear esta filtración? Probablemente voy a hacer esto en mi propio tiempo, por lo que cualquier enfoque es viable.

¿Fue útil?

Solución

Una técnica que probarí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 de pequeño que demuestre el mismo problema, será mucho más fácil de entender. Quizás el problema de memoria se aclare en ese punto.

Otros consejos

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 ella depura estas cosas te dará mucha información para saber qué está pasando con tu problema.

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

Utilizo el dotTrace profiler para rastrear pérdidas de memoria. Es mucho más determinista que el ensayo y error metodológico y los resultados son mucho más rápidos.

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. La comparación de los dos le mostrará todos los objetos que se crearon en el medio pero que no se liberaron. Luego puede ver el marco de la pila en el momento de su creación y, por lo tanto, averiguar qué instancias no se liberan.

Obtenga esto: http://www.red-gate.com/ Productos / ants_profiler / index.htm

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

  1. Agregar código al constructor de la objeto no administrado para iniciar sesión cuando es onstructed, y ordenar una ID única. Utilice esa ID única cuando el objeto se destruye de nuevo, y puedes en menos diga cuales van extraviada.
  2. Grep el código para cada lugar que construir un nuevo objeto; sigue eso ruta de código para ver si tiene un hacer coincidir destruir.
  3. Agregue punteros de encadenamiento a objetos construidos, por lo que tiene una enlace al objeto construido Antes y después del actual. Entonces puedes barrerlos más tarde.
  4. Agregar contadores de referencia.
  5. ¿Hay un " debug malloc " disponible?

El complemento de depuración administrada en SoS (Hijo del ataque) es inmensamente eficaz para rastrear las "fugas" de la memoria administrada, ya que son, por definición, detectables desde las raíces de gc.

Funcionará en WinDbg o Visual studio (aunque en muchos aspectos es más fácil de usar en WinDbg)

No es nada fácil de entender. Aquí hay un tutorial

También secundaría la recomendación de revisar el blog de Tess Fernández.

¿Cómo sabes a ciencia cierta que realmente tienes una pérdida de memoria?

Otra cosa: 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 puede recopilarlo. Asegúrese de cancelar el registro de todos los controladores de eventos si desea que sus objetos se recolecten como basura.

Tenga cuidado de cómo define " filtración " ;. " 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 de recolección de basura. Simplemente puede ser que GC no haya necesitado recolectar la memoria extra que está usando. También tenga cuidado con la diferencia entre el uso de memoria virtual y el uso de memoria física.

Finalmente, no todas las " fugas de memoria " son causados ??por " memoria " tipo de problemas Una vez me dijeron (no me pidió) que corrigiera una pérdida de memoria urgente que estaba causando 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 grupo de objetos (de un artículo de MSDN) para StringBuilders, y el uso de memoria disminuyó sustancialmente.

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

Bloqueos colocados alrededor de las llamadas al código no administrado y la " pérdida de memoria " se fue.

Si su objeto no administrado realmente es la causa de la fuga, es posible que desee que llame a AddMemoryPressure cuando asigne memoria no administrada y RemoveMemoryPressure en Finalize / Dispose / donde sea que desasigne la memoria no administrada. Esto le dará al GC un mejor manejo de la situación, ya que puede no darse cuenta de que es necesario programar la recopilación de otra manera.

Mencionaste que estás usando eventos. ¿Está eliminando los controladores de esos eventos cuando termine con su objeto? Descubrí que los controladores de eventos 'sueltos' causarán muchos problemas de pérdida de memoria si agrega un grupo de controladores sin eliminarlos cuando termine.

La mejor herramienta de creación de perfiles de memoria para .Net es esta:

http://memprofiler.com

Además, mientras estoy aquí, el mejor perfilador de rendimiento para .Net es este:

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

También tienen una excelente relación calidad-precio, tienen gastos generales bajos y son fáciles de usar. Cualquiera que sea serio acerca del desarrollo de .Net debe considerar ambos como una inversión personal y comprar de inmediato. Ambos tienen una versión de prueba gratuita.

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

En mi humilde opinión, ambos son absolutamente brillantes.

Al igual que Charlie Martin, puedes 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 recrearlo, incluso una o dos veces con la misma identificación de asignación, esto le permitirá ver lo que está sucediendo en ese momento (obviamente, TLS / threading también debe manejarse, si es necesario, pero me fui) para mayor claridad).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top