У меня есть инструмент для сравнения 2 файлов CSV и затем блока каждая ячейка в один из этих 6 блоков. В основном это читает в файлах CSV (использующий быстрого csv читателя, кредит: http://www.codeproject.com/KB/database/CsvReader.aspx), и затем создает словарь, имеющий отношение к каждому файлу на основе ключей, обеспеченных пользователем. Я тогда выполняю итерации через th словари, сравнивающие значения и пишущие файл CSV результата.
В то время как это сверкает быстро, это очень неэффективно с точки зрения использования памяти. Я не могу сравнить файлы больше чем на 150 МБ на своем поле с 3 ГБ физической памяти.
Вот фрагмент кода для чтения ожидаемого файла. В конце этой части использование памяти близко к 500 МБ от диспетчера задач.
// Read Expected
long rowNumExp;
System.IO.StreamReader readerStreamExp = new System.IO.StreamReader(@expFile);
SortedDictionary dictExp = new SortedDictionary();
List listDupExp = new List();
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");
}
Есть ли что-нибудь, я мог сделать для создания этого большей памятью эффективный? Что-нибудь я мог сделать по-другому выше, для потребления меньшего количества mermory?
Любые идеи приветствуются.
Спасибо парни для всей обратной связи.
Я включил изменения, как предложено сохранить индекс строки вместо самой строки в словарях.
Вот тот же фрагмент кода с новой реализацией.
// Read Expected
long rowNumExp;
SortedDictionary dictExp = new SortedDictionary();
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();
Однако проблема, мне нужны оба наборы данных в памяти. Я на самом деле выполняю итерации и через словари, ища соответствия, несоответствия, дубликаты и через уволенных на основе ключа.
Используя этот подход хранения индекса строки, я все еще использую большую память, потому что для динамического доступа я должен теперь использовать кэшированную версию csv читателя. Таким образом, хотя словарь является значительно уменьшенным теперь, кэширование данных восполняет сбережения, и я все еще закончил приблизительно с подобным использованием памяти.
Надежда, я имею смысл...:)
Одна опция состоит в том, чтобы избавиться от словаря полностью и просто цикла через эти 2 файла, но не уверенная, если производительность с такой скоростью, как сравнила бы 2 словаря.
Любые исходные данные очень ценятся.
Если вы еще не получили профилировщик на этом, как Dottrace, чтобы увидеть, как объекты, использующие память, которые даст вам хорошее представление о том, что нужно оптимизировать.
Некоторые идеи посмотреть код:
Вам нужно хранить listdupexp? Мне кажется в списке, вы эффективно загружаете оба файла в память, так что 2 × 150 МБ + некоторые накладные расходы могут легко подойти 500 МБ в диспетчере задач.
Во-вторых, вы можете начать запись вывода, прежде чем прочитать все вход? Я предполагаю, что это сложно, как выглядит так, будто вам нужны все выходные элементы, отсортированные, прежде чем писать их, но, возможно, что вы могли бы посмотреть.
Скажите, если я получу что-нибудь не так.
Код выше считывает один файл CSV и ищет дублирующиеся клавиши. Каждая строка входит в один из двух наборов, для дублирующихся клавиш, и один без.
Что вы делаете с этими врезанами?
Пишены ли они в разные файлы?
Если так, что нет никаких причин хранить неразрешенные строки в списке, как вы найдете их, пишите их в файл.
Когда вы найдуте дубликаты, нет необходимости хранить всю строку, просто храните ключ и напишите строку в файл (очевидно, другой файл, если вы хотите сохранить их отдельные).
Если вам нужно сделать дальнейшую обработку на различных наборах, а затем вместо хранения всей строки, если не хранить номер строки. Тогда, когда вы делаете то, что это за когда-либо, вы делаете со строками, у вас есть нумерация строки, чтобы снова получить ряд.
NB: вместо того, чтобы сохранить номер строки, вы можете хранить смещение в файле пункт начальной точки строки. Затем вы можете получить доступ к файлу и прочитать строки случайным образом, если вам нужно.
Просто прокомментируйте этот ответ с любыми вопросами (или разъяснениями), которые вы могли бы обновить ответ, я буду здесь еще пару часов.
Редактировать
Вы можете уменьшить печать ног памяти, не сохраняя клавиши, а сохранение хэшей клавиш. Если вы найдете дубликат, обратитесь к этой позиции в файле, перечитайте строку и сравните фактические клавиши.
Вы можете заменить keyExp
с помощью StringBuilder. перераспределение строки в таком цикле будет продолжать выделять больше памяти, поскольку строки неизменяемы.
StringBuilder keyExp = new StringBuilder();
...
keyExp.Append("|" + readerCSVExp[i - 1]) ;
...
многие ли струны одинаковы? вы можете попробовать интернировать их , тогда любые идентичные строки будут использовать одну и ту же память, а не будут копиями ...
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);
}
Я не уверен, как именно работает код, но ... кроме этого, я бы скажем, вам не нужно хранить rowExp
в словаре, сохраните что-нибудь еще, например число, и запишите rowExp
обратно на диск в другом файле. Это, вероятно, сэкономит вам больше всего памяти, поскольку это кажется массивом строк из файла, поэтому, вероятно, он большой. Если вы записываете его в файл и сохраняете номер в файле, то вы можете вернуться к нему снова в будущем, если вам нужно будет обработать. Если вы сохранили смещение в файле как значение в словаре, вы сможете быстро найти его снова. Может быть :).