Словарь C # и эффективное использование памяти - PullRequest
9 голосов
/ 29 января 2010

У меня есть инструмент для сравнения 2 CSV-файлов, а затем объединить каждую ячейку в одно из 6 сегментов. По сути, он считывает файлы csv (используя программу быстрого чтения csv, credit: http://www.codeproject.com/KB/database/CsvReader.aspx), а затем создает словарь, относящийся к каждому файлу, на основе ключей, предоставленных пользователем. Затем я перебираю словари, сравнивая значения и написание файла результатов csv.

Несмотря на то, что он работает быстро, он очень неэффективен с точки зрения использования памяти. Я не могу сравнить более 150 МБ файлов на моем устройстве с 3 ГБ физической памяти.

Вот фрагмент кода для чтения ожидаемого файла. В конце этой части использование памяти близко к 500 МБ от диспетчера задач.

// 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");                
}

Есть ли что-нибудь, что я мог бы сделать, чтобы сделать его более эффективным для памяти? Что-нибудь, что я мог бы сделать иначе, чтобы потреблять меньше наемников?

Любые идеи приветствуются.

Спасибо, ребята, за все отзывы.

Я включил изменения, как предлагалось, для сохранения индекса словаря вместо самой строки в словарях.

Вот тот же фрагмент кода с новой реализацией.

// 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();

Однако проблема в том, что мне нужны оба набора данных в памяти. Я на самом деле перебираю оба словаря в поисках совпадений, несоответствий, дубликатов и отсева на основе ключа.

Используя этот подход для хранения индекса строки, я все еще использую много памяти, потому что для динамического доступа мне теперь нужно использовать кэшированную версию программы чтения csv. Таким образом, хотя словарь теперь стал намного меньше, кеширование данных компенсирует экономию, и я все равно обнаружил примерно такое же использование памяти.

Надеюсь, я понимаю ...:)

Один из вариантов - полностью избавиться от словаря и просто перебрать 2 файла, но не уверен, что производительность будет выше, чем при сравнении двух словарей.

Любые входные данные высоко ценятся.

Ответы [ 3 ]

7 голосов
/ 29 января 2010

Вы можете заменить 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 назад. на диск в другом файле. Это, вероятно, сэкономит вам больше памяти, так как это кажется массив строк из файла, поэтому, вероятно, большой. Если вы записываете его в файл и сохраняете номер в файле, то в будущем вы можете вернуться к нему снова в будущем, если вам потребуется обработка. Если вы сохранили смещение в файле как значение в словаре, вы сможете быстро найти его снова. Возможно:).

3 голосов
/ 29 января 2010

Скажите, если я что-то не так.

Код выше читает один файл CSV и ищет дубликаты ключей. Каждый ряд входит в один из двух наборов: один для дублирующих ключей и один без.

Что вы делаете с этими наборами строк?

Они записаны в разные файлы?

Если это так, то нет смысла хранить строки, не являющиеся необновленными, в списке, поскольку вы обнаружите, что они записывают их в файл.

Когда вы находите дубликаты, нет необходимости хранить всю строку, просто сохраняйте ключ и записывайте строку в файл (очевидно, другой файл, если вы хотите, чтобы они были разделены).

Если вам нужно выполнить дальнейшую обработку на разных наборах, то вместо сохранения всей строки, когда не сохраните номер строки. Затем, когда вы делаете то, что когда-либо, вы делаете со строками, у вас есть номер строки, необходимый для повторного извлечения строки.

Примечание: вместо сохранения номера строки вы можете сохранить смещение в файле начальной точки строки. Затем вы можете получить доступ к файлу и читать строки случайным образом, если вам нужно.

Просто прокомментируйте этот ответ любыми вопросами (или разъяснениями), которые у вас могут возникнуть, я обновлю ответ, я все равно буду здесь еще пару часов.

Редактировать
Вы можете уменьшить объем отпечатка памяти, не храня ключи, а сохранив их хеши. Если вы найдете дубликат, найдите эту позицию в файле, перечитайте строку и сравните фактические ключи.

2 голосов
/ 29 января 2010

Если у вас еще нет профилировщика, такого как DotTrace, чтобы увидеть, какие объекты используют память, это даст вам хорошее представление о том, что нужно оптимизировать.

Некоторые идеи, глядя на код:

Вам нужно хранить список DupExp? Мне кажется, со списком вы эффективно загружаете оба файла в память, поэтому 2 x 150 МБ + некоторые накладные расходы могут легко достигнуть 500 МБ в диспетчере задач.

Во-вторых, можете ли вы записать вывод до того, как прочитаете все входные данные? Я предполагаю, что это сложно, так как похоже, что вам нужно отсортировать все выходные элементы перед тем, как вы их напишите, но, возможно, это то, на что вы можете посмотреть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...