El proceso de C # intensivo de RAM se vuelve más lento después de varias horas

c# html-agility-pack memory-management multithreading performance

Pregunta

Ejecuto un proceso (servicio) de C # en un servidor responsable de analizar las páginas HTML continuamente. Se basa en HTMLAgilityPack. El síntoma es que se vuelve más lento y más lento a medida que pasa el tiempo.

Cuando comienzo el proceso, maneja n páginas / s. Después de unas pocas horas, la velocidad desciende a alrededor de n / 2 páginas / s. Puede bajar a n / 10 después de unos días. El fenómeno se ha observado muchas veces y es bastante determinista. Cada vez que se reinicia el proceso, las cosas vuelven a la normalidad.

Muy importante: puedo ejecutar otros cálculos en el mismo proceso y no se ralentizan: puedo alcanzar el 100% de la CPU con cualquier cosa que desee en cualquier momento. El proceso en sí no es lento. Sólo el análisis de HTML se ralentiza.

Podría reproducirlo con un código mínimo (en realidad, el comportamiento en el servicio original es un poco más extremo, pero aún así este código reproduce el comportamiento):

public static void Main(string[] args) {
    string url = "https://en.wikipedia.org/wiki/History_of_Texas_A%26M_University";
    string html = new HtmlWeb().Load(url).DocumentNode.OuterHtml;
    while (true) {
        //Processing
        Stopwatch sw = new Stopwatch();
        sw.Start();
        Parallel.For(0, 10000, i => new HtmlDocument().LoadHtml(html));
        sw.Stop();
        //Logging
        using(var writer = File.AppendText("c:\\parsing.log")) {
            string text = DateTime.Now.ToString() + ";" + (int) sw.Elapsed.TotalSeconds;
            writer.WriteLine(text);
            Console.WriteLine(text);
        }
    }
}

Con este código mínimo, muestra la velocidad (páginas por segundo) en función del número de horas transcurridas desde que se inició el proceso:

introduzca la descripción de la imagen aquí

Todas las causas obvias han sido descartadas:

  • Las páginas HTML son más grandes o diferentes (en el código mínimo es la misma página)
  • RAM completa: el proceso utiliza alrededor de 500 MB en 32 GB
  • Otros procesos utilizan CPU o RAM.

Podría ser algo acerca de la memoria RAM y la asignación de memoria. Sé que HTMLAgilityPack hace una gran cantidad de asignación de memoria de objetos pequeños (nodos HTML y cadenas). Está claro que la asignación de memoria y los subprocesos múltiples no funcionan bien juntos. Pero no entiendo cómo el proceso puede volverse más lento y más lento.

¿Sabe de algo sobre CLR o Windows que podría estar causando que el procesamiento intensivo de RAM (muchas asignaciones) se vuelva más lento y más lento? ¿Como, por ejemplo, penalizar los hilos haciendo asignaciones de memoria de una manera determinada?

Respuesta aceptada

He notado un comportamiento similar con el HTMLAgilityPack.

Descubrí que cuando los datos de un rendimiento comienzan a separar las fugas locales en el compilador, se generan clases que comienzan a causar problemas. Como no hay código disponible ... bla bla, aquí está mi botiquín de primeros auxilios

  1. Asegúrese de establecer la estrategia correcta , cambiar la estrategia de recopilación de GC en app.config ayudará a la fragmentación.
  2. Asegúrese de anular las cosas cuando no las necesite, tan pronto como no las necesite, no espere a que el alcance limpie su memoria, ya que los IEnumerables son llamados en el método de llamada y el alcance de los métodos disponibles y pueden vivir mucho más tiempo. ¡de lo que piensas! Abra su código en ILSpy y observe las clases clasificadas <> d__0 (0) . Verás cosas generadas como d __. X = X; En este caso X podría contener un fragmento o una página entera.
  3. Las variables locales se elevan al montón, ya que no se puede acceder a ellas en las iteraciones de IEnumable si no estuvieran allí.
  4. El bloqueo comienza a convertirse en un problema, los elementos grandes se vuelven borrosos en su ram de 4ta generación que en realidad comenzarán a bloquear el GC. El GC está pausando sus hilos para poder realizar la recolección de basura.
  5. Lo peor de HTMLAgility es que se fragmenta y termina siendo un problema real.

    Estoy bastante seguro de que cuando comience a considerar el alcance de sus fragmentos de HTML encontrará que las cosas comenzarán a ir bien. Eche un vistazo a su ejecución usando WinDbg en SOS y haga un volcado de su memoria y eche un vistazo.

Como hacer eso.

  1. abra WinDebug, presione F6 y adjunte al proceso (ingrese la ID del proceso en el campo y presione ok)
  2. luego carga la ejecución en tu memoria ingresando

    .loadby sos clr

  3. luego entra

    ! dumpheap -stat

A continuación, obtendría los elementos de memoria asignados en su aplicación con la dirección de la memoria y el tamaño agrupados por tipo y ordenados de encabezado bajo a encabezado alto, verá algo como System.String [] con un número masivo delante, eso es El sufijo que te gustaría investigar primero.

Ahora a ver quien tiene que escribir.

!dumpheap -mt <heap address>

Y verá las direcciones que utilizan esa tabla de memoria (MT) y el tamaño del ram que utiliza.

Ahora se vuelve interesante, en lugar de ir a través de x100 líneas de yode que escribe

!gcroot <address>

lo que se imprimirá es el archivo y la línea de código que asignaron la memoria, la clase generada por el compilador y el varaible que le causan dolor, así como los bytes que contiene.

Esto es lo que uno podría llamar "depuración de producción" y funciona si tiene acceso al servidor, que supongo que tiene.

Espero haber sido de ayuda,

Walter



Related

Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué