2010-01-04 15 views
6

Edit2: Solo quiero asegurarme de que mi pregunta sea clara: ¿Por qué, en cada iteración de AppendToLog(), la aplicación usa 15mb más? (el tamaño del archivo de registro original)¿Dónde está la pérdida de memoria en esta función?

Tengo una función llamada AppendToLog() que recibe la ruta del archivo de un documento HTML, realiza algunos análisis y lo agrega a un archivo. Se llama así:

this.user_email = uemail; 
string wanted_user = wemail; 

string[] logPaths; 
logPaths = this.getLogPaths(wanted_user); 

foreach (string path in logPaths) 
{    

    this.AppendToLog(path);     

} 

En cada iteración, el uso de RAM aumenta en 15 mp aproximadamente. Esta es la función: (parece largo, pero es muy sencillo)

public void AppendToLog(string path) 
{ 

Encoding enc = Encoding.GetEncoding("ISO-8859-2"); 
StringBuilder fb = new StringBuilder(); 
FileStream sourcef; 
string[] messages; 

try 
{ 
    sourcef = new FileStream(path, FileMode.Open); 
} 
catch (IOException) 
{ 
    throw new IOException("The chat log is in use by another process."); ; 
} 
using (StreamReader sreader = new StreamReader(sourcef, enc)) 
{ 

    string file_buffer; 
    while ((file_buffer = sreader.ReadLine()) != null) 
    { 
     fb.Append(file_buffer); 
    }     
} 

//Array of each line's content 
messages = parseMessages(fb.ToString()); 

fb = null; 

string destFileName = String.Format("{0}_log.txt",System.IO.Path.GetFileNameWithoutExtension(path)); 
FileStream destf = new FileStream(destFileName, FileMode.Append); 
using (StreamWriter swriter = new StreamWriter(destf, enc)) 
{ 
    foreach (string message in messages) 
    { 
     if (message != null) 
     { 
      swriter.WriteLine(message); 
     } 
    } 
} 

messages = null; 

sourcef.Dispose(); 
destf.Dispose(); 


sourcef = null; 
destf = null; 
} 

he estado días con esto y no sé qué hacer :(

Editar: Esta es ParseMessages, una función que utiliza HtmlAgilityPack para despojar partes de un registro de HTML.

public string[] parseMessages(string what) 
{ 
StringBuilder sb = new StringBuilder(); 
HtmlDocument doc = new HtmlDocument(); 

doc.LoadHtml(what);    

HtmlNodeCollection messageGroups = doc.DocumentNode.SelectNodes("//body/div[@class='mplsession']"); 
int messageCount = doc.DocumentNode.SelectNodes("//tbody/tr").Count; 

doc = null; 

string[] buffer = new string[messageCount]; 

int i = 0; 

foreach (HtmlNode sessiongroup in messageGroups) 
{ 
    HtmlNode tablegroup = sessiongroup.SelectSingleNode("table/tbody"); 

    string sessiontime = sessiongroup.Attributes["id"].Value; 

    HtmlNodeCollection messages = tablegroup.SelectNodes("tr"); 
    if (messages != null) 
    { 
     foreach (HtmlNode htmlNode in messages) 
     { 
      sb.Append(
        ParseMessageDate(
         sessiontime, 
         htmlNode.ChildNodes[0].ChildNodes[0].InnerText 
        ) 
       ); //Date 
      sb.Append(" "); 

      try 
      { 
       foreach (HtmlTextNode node in htmlNode.ChildNodes[0].SelectNodes("text()")) 
       { 
        sb.Append(node.Text.Trim()); //Name 
       } 
      } 
      catch (NullReferenceException) 
      { 
       /* 
       * We ignore this exception, it just means there's extra text 
       * and that means that it's not a normal message 
       * but a system message instead 
       * (i.e. "John logged off") 
       * Therefore we add the "::" mark for future organizing 
       */ 
       sb.Append("::"); 
      } 
      sb.Append(" "); 

      string message = htmlNode.ChildNodes[1].InnerHtml; 
      message = message.Replace(""", "'"); 
      message = message.Replace(" ", " "); 
      message = RemoveMedia(message); 
      sb.Append(message); //Message 
      buffer[i] = sb.ToString(); 
      sb = new StringBuilder(); 
      i++; 
     } 
    } 
} 
messageGroups = null; 
what = null; 
return buffer; 
} 
+3

¿Qué es parseMessages? – Fredou

+0

Allí, lo agregaron. –

+0

No necesita 'FileStream' si finalmente usa' StreamReader'. Mira los constructores. –

Respuesta

5

Como muchos han mencionado, este es probablemente sólo un artefacto de la GC no limpiar la memoria de almacenamiento tan rápido como usted está esperando que lo haga. Esto es normal para los lenguajes administrados, como C#, Java, etc. Realmente necesita averiguar si la memoria asignada a su programa es gratuita o no, si le interesa ese uso. Las preguntas que debe formular al respecto son:

  1. ¿Cuánto tiempo lleva funcionando su programa? ¿Es un programa de tipo de servicio que se ejecuta continuamente?
  2. Durante el lapso de ejecución, ¿continúa asignando memoria del sistema operativo o si alcanza un estado estable? (¿Lo has corrido el tiempo suficiente para descubrirlo?)

Su código no se ve como que va a tener una "memoria de fugas". En lenguajes administrados realmente no obtener pérdidas de memoria como lo haría en C/C++ (a menos que esté utilizando inseguras o externas que son las bibliotecas C/C++). Lo que pasa es que aunque sí es necesario tener cuidado con las referencias que se mantienen alrededor o están ocultos (como una clase de colección que se ha dicho para eliminar un elemento, pero no fija el elemento de la matriz interna a null). En general, los objetos con referencias en la pila (locales y parámetros) no pueden 'perder' a menos que almacene la referencia del objeto (s) en una variable de objeto/clase.

Algunos comentarios sobre su código:

  1. Puede reducir la asignación/desasignación de la memoria por la pre-asignación de la StringBuilder a por lo menos el tamaño adecuado. Como sabe que necesitará mantener todo el archivo en la memoria, asígnelo al tamaño del archivo (esto realmente le dará un buffer que es un poco más grande que el requerido ya que no está almacenando secuencias de caracteres de nueva línea, pero el archivo probablemente los tiene):

    FileInfo fi = new FileInfo(path); 
    StringBuilder fb = new StringBuilder((int) fi.Length); 
    

    es posible que desee asegurarse de que el archivo existe antes de conseguir su longitud, utilizando fi para comprobar eso. Tenga en cuenta que simplemente bajé la longitud a int sin verificar ningún error, ya que sus archivos tienen menos de 2 GB según el texto de su pregunta. Si ese no es el caso, entonces debes verificar la longitud antes de enviarla, quizás lanzando una excepción si el archivo es demasiado grande.

  2. recomendaría la eliminación de todas las variable = null declaraciones en su código. No son necesarios ya que son variables asignadas de pila. Además, en este contexto, no ayudará al GC, ya que el método no durará mucho tiempo. Por lo tanto, tenerlos crea un desorden adicional en el código y es más difícil de entender.

  3. En su método ParseMessages, usted toma un NullReferenceException y asume que es simplemente un nodo sin texto. Esto podría conducir a problemas confusos en el futuro. Puesto que esto es algo que se espera que suceda normalmente como resultado de algo que pueda existir en los datos usted debe comprobar para la condición en el código, tales como:

    if (node.Text != null) 
        sb.Append(node.Text.Trim()); //Name 
    

    Las excepciones son para condiciones excepcionales/inesperados en el código. Asignar significados significativos a NullReferenceException más que eso había una referencia nula puede (probablemente) ocultar errores en otras partes de ese mismo bloque try ahora o con cambios futuros.

+0

Parece que tenía razón, no hay pérdida de memoria. Y gracias por los comentarios en mi código, todavía estoy agarrando C#. –

1

una cosa que puede que desee probar, está forzando temporalmente un GC.Collect después de cada carrera. El GC es muy inteligente, y no recuperar la memoria hasta se siente el gasto de una colección vale la pena el valor de cualquier memoria recuperada.

Editar: Solo quería agregar que es importante entender que llamar a GC.Collect manualmente es una mala práctica (para cualquier caso de uso normal. Anormal == quizás una función de carga para un juego o algo así). Debe dejar que el recolector de basura decida cuál es el mejor, ya que generalmente tendrá más información que la disponible sobre los recursos del sistema y elementos similares en los que basar su comportamiento de recopilación.

+2

¡no olvides quitarlo después !, no guardes el recopilatorio allí, mala idea – Fredou

+0

jaja, estaba escribiendo eso, gracias :) – Gregory

0

Limpiaría manualmente la matriz de mensaje y el generador de cadenas antes de establecerlos como nulos.

edición

mirando lo que parece el proceso de hacer Tengo una sugerencia, si no es demasiado tarde en lugar de analizar un archivo html.

crear un esquema de conjunto de datos y usarlo para escribir y leer un archivo de registro xml y usar un archivo xsl para convertirlo en un archivo html.

+0

¿Podrías dar más detalles sobre este último punto, por favor? No quiero crear otro archivo HTML, el objetivo de mi aplicación es crear una versión simplificada de los voluminosos registros HTML: P –

0

El bloque try-catch podría usar finalmente (limpieza). Si observa lo que hace la instrucción using, es equivalente a try catch finalmente. Sí, correr GC también es una buena idea. Sin compilar el código y darle una oportunidad, es difícil decir con seguridad ...

Además, deshacerse de este tipo adecuadamente usando un usando:

FileStream destf = new FileStream (destFileName, FileMode.APPEND);

Busque efectiva C edición # 2 de

2

Me gustaría mirar cuidadosamente qué necesita para pasar una cadena a parseMessages, es decir fb.ToString().

El comentario de su código dice que esto devuelve una matriz del contenido de cada línea. Sin embargo, en realidad está leyendo todas las líneas del archivo de registro en fb y luego convirtiendo a una cadena.

Si está analizando archivos de gran tamaño en parseMessages() puede hacer esto mucho más eficiente pasando el StringBuilder mismo o el StreamReader a parseMessages(). Esto habilitaría solo la carga de una porción del archivo en la memoria en cualquier momento, en lugar de usar ToString() que actualmente obliga a todo el archivo de registro a la memoria.

que son menos propensos a tener una verdadera pérdida de memoria en una aplicación .NET gracias a la recolección de basura. No parece estar utilizando ningún recurso grande, como archivos, por lo que parece incluso menos probable que tenga una pérdida de memoria real.

Parece que usted ha dispuesto de los recursos ok, sin embargo, la GC es probablemente luchando para asignar y desasignar continuación, los trozos grandes de memoria en tiempo antes de que se inicia la siguiente iteración, y por lo que ver el uso de memoria en aumento.

Mientras GC.Collect() puede permitirle a la fuerza de cancelación de asignación de memoria, yo fuertemente aconsejaría buscar en las sugerencias anteriores antes de recurrir a tratar de manejar manualmente la memoria a través de GC.

[Actualización] Al ver su parseMessages() y el uso de HtmlAgilityPack (una biblioteca muy útil, por cierto) parece probable que haya algunas asignaciones de memoria grandes y posiblemente numerosas para cada logil.

HtmlAgility asigna memoria para varios nodos internamente, cuando se combina con su matriz de búfer y las asignaciones en la función principal, estoy aún más seguro de que el GC está sometido a mucha presión para mantener el ritmo.

Para dejar de adivinar y obtener algunas métricas reales, ejecutaría ProcessExplorer y agregaría las columnas para mostrar las columnas de colecciones GC Gen 0,1,2. Luego ejecute su aplicación y observe el número de colecciones. Si está viendo números grandes en estas columnas, entonces el GC tiene dificultades y debe rediseñar para utilizar menos asignaciones de memoria.

Alternativamente, la libre CLR Profiler 2.0 de Microsoft proporciona una representación visual agradable de las asignaciones de memoria dentro de la aplicación .NET.

+0

"Sin embargo, en realidad está leyendo todas las líneas del archivo de registro en fb y luego convirtiendo a una cuerda." Sí, porque luego parseMessages() usa HtmlAgilityPack para eliminar el archivo. –

+0

@Daniel, HtmlAgilityPack también puede leer de un Stream como StreamReader, etc. (pasarlo al método Load()). El uso de un Stream le permite evitar cargar toda la cadena/archivo en la memoria. – Ash

0

No veo ninguna pérdida obvia de memoria; mi primera suposición sería que es algo en la biblioteca.

Una buena herramienta para entender este tipo de cosas a cabo es el .NET memoria de perfiles, por El SciTech. Tienen una prueba gratuita de dos semanas.

A falta de eso, se podría tratar de comentar algunas de las funciones de la biblioteca, y ver si el problema desaparece si usted acaba de leer los archivos y no hacer nada con los datos.

Además, ¿dónde está buscando estadísticas de uso de memoria? Tenga en cuenta que las estadísticas informadas por el Administrador de tareas no siempre son muy útiles o reflejan el uso real de la memoria.

4

No hay pérdida de memoria. Si está usando el Administrador de tareas de Windows para medir la memoria utilizada por su aplicación .NET, no obtiene una imagen clara de lo que está sucediendo, porque el GC administra la memoria de una manera compleja que el Administrador de tareas no refleja.

Un ingeniero de MS escribió un gran article acerca de por qué las aplicaciones .NET que parecen tener pérdidas de memoria probablemente no lo sean, y tiene enlaces a explicaciones muy detalladas de cómo funciona realmente el GC. Todo programador de .NET debería leerlos.

+0

Yo marcaría esto como aceptado también, pero no puedo elegir 2 respuestas. ¡Gracias! –

0

clase HtmlDocument (por lo que yo puedo determin) tiene una pérdida de memoria graves cuando se utilicen desde el código administrado. Recomiendo usar el analizador XMLDOM en su lugar (aunque esto requiere documentos bien formados, pero eso es otro +).

+0

Nunca he oído hablar de un problema grave de pérdida de memoria con HtmlDocument. ¿Podría citar una referencia o dar un ejemplo? –

Cuestiones relacionadas