2010-01-29 25 views
9

Tengo una herramienta para comparar 2 archivos csv y luego dividir cada celda en uno de los 6 segmentos. Básicamente, lee en los archivos csv (usando el lector de csv rápido, crédito: http://www.codeproject.com/KB/database/CsvReader.aspx) y luego crea un diccionario perteneciente a cada archivo basado en las claves proporcionadas por el usuario. Luego recorro los diccionarios examinando los valores y escribiendo un resultado en un archivo csv.C# Dictionary y uso eficiente de la memoria

Si bien es extremadamente rápido, es muy ineficiente en términos de uso de memoria. No puedo comparar más de 150 MB de archivos en mi caja con 3 GB de memoria física.

Aquí hay un fragmento de código para leer el archivo esperado. Al final de este artículo, el uso de memoria está cerca de 500 MB del administrador de tareas.

// Read Expected 
long rowNumExp; 
System.IO.StreamReader readerStreamExp = new System.IO.StreamReader(@expFile); 
SortedDictionary<string, string[]> dictExp = new SortedDictionary<string, string[]>(); 
List<string[]> listDupExp = new List<string[]>(); 
using (CsvReader readerCSVExp = new CsvReader(readerStreamExp, hasHeaders, 4096)) 
{ 
    readerCSVExp.SkipEmptyLines = false; 
    readerCSVExp.DefaultParseErrorAction = ParseErrorAction.ThrowException; 
    readerCSVExp.MissingFieldAction = MissingFieldAction.ParseError; 
    fieldCountExp = readerCSVExp.FieldCount;     
    string keyExp; 
    string[] rowExp = null; 
    while (readerCSVExp.ReadNextRecord()) 
    { 
     if (hasHeaders == true) 
     { 
      rowNumExp = readerCSVExp.CurrentRecordIndex + 2; 
     } 
     else 
     { 
      rowNumExp = readerCSVExp.CurrentRecordIndex + 1; 
     } 
     try 
     { 
      rowExp = new string[fieldCount + 1];      
     } 
     catch (Exception exExpOutOfMemory) 
     { 
      MessageBox.Show(exExpOutOfMemory.Message); 
      Environment.Exit(1); 
     }     
     keyExp = readerCSVExp[keyColumns[0] - 1]; 
     for (int i = 1; i < keyColumns.Length; i++) 
     { 
      keyExp = keyExp + "|" + readerCSVExp[i - 1]; 
     } 
     try 
     { 
      readerCSVExp.CopyCurrentRecordTo(rowExp); 
     } 
     catch (Exception exExpCSVOutOfMemory) 
     { 
      MessageBox.Show(exExpCSVOutOfMemory.Message); 
      Environment.Exit(1); 
     } 
     try 
     { 
      rowExp[fieldCount] = rowNumExp.ToString(); 
     } 
     catch (Exception exExpRowNumOutOfMemory) 
     { 
      MessageBox.Show(exExpRowNumOutOfMemory.Message); 
      Environment.Exit(1); 
     } 
     // Dedup Expected       
     if (!(dictExp.ContainsKey(keyExp))) 
     { 
      dictExp.Add(keyExp, rowExp);       
     } 
     else 
     { 
      listDupExp.Add(rowExp); 
     }      
    }     
    logFile.WriteLine("Done Reading Expected File at " + DateTime.Now); 
    Console.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n"); 
    logFile.WriteLine("Done Creating Expected Dictionary at " + DateTime.Now); 
    logFile.WriteLine("Done Identifying Expected Duplicates at " + DateTime.Now + "\r\n");     
} 

¿Hay algo que pueda hacer para que sea más eficiente con la memoria? ¿Hay algo que pueda hacer de manera diferente anteriormente, consumir menos mermelada?

Cualquier idea es bienvenida.

Gracias a todos por los comentarios.

He incorporado los cambios según lo sugerido para almacenar el índice de la fila en lugar de la misma fila en los diccionarios.

Aquí está el mismo fragmento de código con la nueva implementación.

// Read Expected 
     long rowNumExp; 
     SortedDictionary<string, long> dictExp = new SortedDictionary<string, long>(); 
     System.Text.StringBuilder keyExp = new System.Text.StringBuilder(); 
     while (readerCSVExp.ReadNextRecord()) 
     { 
      if (hasHeaders == true) 
      { 
       rowNumExp = readerCSVExp.CurrentRecordIndex + 2; 
      } 
      else 
      { 
       rowNumExp = readerCSVExp.CurrentRecordIndex + 1; 
      } 
      for (int i = 0; i < keyColumns.Length - 1; i++) 
      { 
       keyExp.Append(readerCSVExp[keyColumns[i] - 1]); 
       keyExp.Append("|"); 
      } 
      keyExp.Append(readerCSVExp[keyColumns[keyColumns.Length - 1] - 1]); 
      // Dedup Expected      
      if (!(dictExp.ContainsKey(keyExp.ToString()))) 
      { 
       dictExp.Add(keyExp.ToString(), rowNumExp); 
      } 
      else 
      { 
       // Process Expected Duplicates   
       string dupExp; 
       for (int i = 0; i < fieldCount; i++) 
       { 
        if (i >= fieldCountExp) 
        { 
         dupExp = null; 
        } 
        else 
        { 
         dupExp = readerCSVExp[i]; 
        } 
        foreach (int keyColumn in keyColumns) 
        { 
         if (i == keyColumn - 1) 
         { 
          resultCell = "duplicateEXP: '" + dupExp + "'"; 
          resultCell = CreateCSVField(resultCell); 
          resultsFile.Write(resultCell); 
          comSumCol = comSumCol + 1; 
          countDuplicateExp = countDuplicateExp + 1; 
         } 
         else 
         { 
          if (checkPTColumns(i + 1, passthroughColumns) == false) 
          { 
           resultCell = "'" + dupExp + "'"; 
           resultCell = CreateCSVField(resultCell); 
           resultsFile.Write(resultCell); 
           countDuplicateExp = countDuplicateExp + 1; 
          } 
          else 
          { 
           resultCell = "PASSTHROUGH duplicateEXP: '" + dupExp + "'"; 
           resultCell = CreateCSVField(resultCell); 
           resultsFile.Write(resultCell); 
          } 
          comSumCol = comSumCol + 1; 
         } 
        } 
        if (comSumCol <= fieldCount) 
        { 
         resultsFile.Write(csComma); 
        } 
       } 
       if (comSumCol == fieldCount + 1) 
       { 
        resultsFile.Write(csComma + rowNumExp); 
        comSumCol = comSumCol + 1; 
       } 
       if (comSumCol == fieldCount + 2) 
       { 
        resultsFile.Write(csComma); 
        comSumCol = comSumCol + 1; 
       } 
       if (comSumCol > fieldCount + 2) 
       { 
        comSumRow = comSumRow + 1; 
        resultsFile.Write(csCrLf); 
        comSumCol = 1; 
       } 
      } 
      keyExp.Clear(); 
     } 
     logFile.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n"); 
     Console.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n"); 
     logFile.WriteLine("Done Analyzing Expected Duplicates at " + DateTime.Now + "\r\n"); 
     Console.WriteLine("Done Analyzing Expected Duplicates at " + DateTime.Now + "\r\n"); 
     logFile.Flush(); 

Sin embargo, el problema es que necesito ambos conjuntos de datos en la memoria. De hecho, repito los diccionarios buscando coincidencias, discrepancias, duplicados y abandonos según la clave.

Usando el método de almacenar el índice de fila, todavía estoy usando mucha memoria porque para el acceso dinámico ahora tengo que usar la versión almacenada en caché del lector csv. Entonces, aunque el diccionario es mucho más pequeño ahora, el almacenamiento en caché de datos compensa los ahorros y aún así terminé con el uso de memoria similar.

Esperanza, yo hago sentido ... :)

Una opción es deshacerse del diccionario completo y el bucle sólo a través de los 2 archivos, pero no está seguro si el rendimiento sería tan rápido como la comparación de 2 diccionarios.

Cualquier entrada es muy apreciada.

+0

en lugar de almacenar en caché el lector csv, ¿no puede almacenar en caché las ubicaciones de registro en el archivo, para que pueda recuperar los registros más tarde? Cuando iteras por los diccionarios buscando deserciones, etc., ¿estás mirando los datos reales o solo las teclas? –

+0

¿Intentó internar la cadena antes de ingresar al diccionario? ¿Se hace la diferencia? ¿Ha ayudado algo de esto con el uso de la memoria? –

Respuesta

7

Puede reemplazar keyExp por un StringBuilder. reasignar la cadena en un bucle así mantendrá la asignación de más memoria ya que las cadenas son inmutables.

StringBuilder keyExp = new StringBuilder(); 
... 
    keyExp.Append("|" + readerCSVExp[i - 1]) ; 
... 

son muchas las cuerdas iguales? usted podría intentar interning them, entonces cualquier cadenas idénticas comparten la misma memoria en lugar de ser copias ...

rowExp[fieldCount] = String.Intern(rowNumExp.ToString()); 

// Dedup Expected    
string internedKey = (String.Intern(keyExp.ToString()));   
if (!(dictExp.ContainsKey(internedKey))) 
{ 
    dictExp.Add(internedKey, rowExp);       
} 
else 
{ 
    listDupExp.Add(rowExp); 
} 

No estoy seguro exactamente cómo funciona el código, pero más allá de eso ... yo diría que Don 't necesita mantener rowExp en el diccionario, mantener algo más, como un número y escribir rowExp volver al disco en otro archivo. Esto probablemente le ahorrará la mayor cantidad de memoria ya que parece ser una matriz de cadenas del archivo, por lo que probablemente sea grande. Si lo escribe en un archivo y conserva el número en el archivo, puede volver a consultarlo en el futuro si necesita procesarlo.Si guardó el desplazamiento en el archivo como el valor en el diccionario, podrá encontrarlo de nuevo rápidamente. Tal vez :).

+0

Interesante, estaba pensando que el compilador/intérprete/jitter/algo internó cadenas automáticamente, pero eso es probablemente solo para picaduras que se sabe que son idénticas en el tiempo de compilación, supongo. – Davy8

+0

@ Davy8, eso es correcto. La interna de cadenas solo ocurre por defecto en cadenas que se crean a partir de constantes de tiempo de compilación. –

3

Dime si me sale algo mal.

El código anterior lee un archivo CSV y busca claves duplicadas. Cada fila entra en uno de dos conjuntos, uno para claves duplicadas y otro sin.

¿Qué hace con estos conjuntos de filas?

¿Están escritos en diferentes archivos?

Si es así, no hay ninguna razón para almacenar las filas que no son únicas en una lista, ya que las encuentra las escribe en un archivo.

Cuando encuentre duplicados, no es necesario almacenar toda la fila, simplemente almacene la clave y escriba la fila en el archivo (obviamente, un archivo diferente si desea mantenerlos separados).

Si necesita realizar un procesamiento adicional en los diferentes conjuntos, en lugar de almacenar toda la fila, cuando no se almacena el número de fila. Luego, cuando hagas lo que sea que hagas con las filas, tienes el número de fila necesario para volver a buscar la fila.

NB: en lugar de almacenar un número de fila, puede almacenar el desplazamiento en el archivo del punto de inicio de la fila. Luego puede acceder al archivo y leer filas al azar, si lo necesita.

Simplemente comenten esta respuesta con cualquier pregunta (o aclaración) que pueda tener, actualizaré la respuesta, de todas formas estaré aquí por un par de horas más.

Editar
Puede reducir la huella de memoria más allá: no almacenar las claves, pero almacenar los hashes de las claves. Si encuentra un duplicado, busque esa posición en el archivo, vuelva a leer la fila y compare las claves reales.

+0

Mire mi respuesta en la publicación editada arriba. Lo sentimos, no sabía cómo pegar correctamente el ejemplo de código en los comentarios. – user262102

2

Si aún no tiene un perfilador de este tipo como DotTrace para ver qué objetos están usando la memoria, eso le dará una buena idea de lo que necesita optimizar.

Algunas ideas de mirar el código:

¿Es necesario almacenar el listDupExp? Me parece que está cargando efectivamente ambos archivos en la memoria, así que 2 x 150MB + algo de sobrecarga podrían acercarse fácilmente a 500MB en el administrador de tareas.

En segundo lugar, ¿puede comenzar a escribir el resultado antes de haber leído todas las entradas? Supongo que esto es complicado, ya que parece que necesita todos los elementos de salida ordenados antes de escribirlos, pero puede ser algo que podría mirar.