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.
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? –
¿Intentó internar la cadena antes de ingresar al diccionario? ¿Se hace la diferencia? ¿Ha ayudado algo de esto con el uso de la memoria? –