Большой массив строк, вызывающий исключение из памяти (C #) - PullRequest
2 голосов
/ 05 января 2012

Я написал приложение c # win, которое позволяет пользователю открывать файл журнала (текст) и просматривать строки журнала в сетке данных. Форматы приложения, которые регистрируют данные, чтобы пользователь мог фильтровать, искать и т. Д.

У меня проблема в том, что когда пользователь открывает файл журнала> 300 МБ, приложение выдает исключение нехватки памяти.

Приложение сначала загружает все строки журнала в массив строк, а затем циклически перебирает строки журнала, добавляя объекты записи журнала в список.

var allLogLines = File.ReadAllLines(logPath).ToList();
var nonNullLogLines = allLogLines.Where(l => !string.IsNullOrEmpty(l));

this.ParseLogEntries(nonNullLogLines.ToArray());

Этот начальный шаг (загрузка данных журнала в массив строк) занимает около 1 ГБ памяти в диспетчере задач.

internal override void ParseLogEntries(string[] logLines)
{
    this.LogEntries = new List<LogEntry>();
    this.LogLinesCount = logLines.Count();

    for (int i = 0; i < this.LogLinesCount; i++)
    {
        int entryStart = this.FindMessageCompartment(logLines, i);
        int entryEnd = this.FindMessageCompartment(logLines, entryStart + 1);
        int entryLength = (entryEnd - entryStart) + 1;

        if (entryStart + entryLength > this.LogLinesCount)
        {
            entryLength = this.LogLinesCount - entryStart;
        }

        var logSection = new string[entryLength];

        Array.Copy(logLines, entryStart, logSection, 0, entryLength);
        Array.Clear(logLines, i, entryLength - 1);

        this.AddLogEntry(logSection);

        i = (entryEnd - 1);
    }
}

Метод AddLogEntry добавляет запись журнала в список (LogEntries). Циклу for удается проанализировать около 50% файла журнала, после чего возникает исключение нехватки памяти. В этот момент диспетчер задач сообщает, что приложение использует около 1,3 ГБ памяти.

Как вы можете видеть выше, я добавил Array.Clear, чтобы обнулить часть данных журнала, которые были успешно проанализированы, в результате я ожидаю, что, когда объекты добавляются в коллекцию, объем памяти ( 1 Гб для начала), используемый большим массивом данных журнала, будет неуклонно сокращаться, но это не так. фактически эта строка не имеет никакого значения для использования памяти, даже если я периодически добавляю сборщик мусора.

Прочитав о LOH, я предполагаю, что это потому, что куча не сжимается, поскольку части большого массива обнуляются, поэтому он всегда использует один и тот же 1 ГБ памяти, несмотря на свое содержимое.

Можно ли каким-либо образом уменьшить объем памяти, удерживаемой во время анализа данных, или возможную переработку, которая может более эффективно использовать память? Мне кажется странным, что текстовый файл размером 300 МБ, помещенный в строковый массив, потребляет 1 ГБ памяти?

Спасибо.

Ответы [ 5 ]

3 голосов
/ 05 января 2012

Вместо вашего метода ParseLogEntries(string[] logLines), который анализирует все строки журнала за один раз, вы могли бы вместо этого использовать метод ParseLogEntry(string logLine), который анализирует одну строку.

Если вы комбинируете это с перебиранием строк в файле журнала по одной (например, создавая себе перечислитель ), это позволит избежать создания большого массива string[] logLines. .

Один из способов может быть таким:

static IEnumerable<string> ReadLines(string filename)
{
    using (TextReader reader = File.OpenText(filename))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

// And use the function somewhere to parse the log

var logEntries = new List<LogEntry>()
foreach (string line in ReadLines("log.txt"))
{
    logEntries.Add(ParseLogEntry(line));
}

Если вы используете .NET 4.0 или новее, вы, конечно, можете просто использовать метод File.ReadLines, как указано в другом ответе sll, вместо создания собственного метода.

1 голос
/ 05 января 2012

Строки требуют непрерывных сегментов памяти в куче;приложение может выбрасывать «Недостаточно памяти» некоторое время, когда в куче много длинных строк и вы пытаетесь выделить другую строку, но у вас нет доступного сегмента необходимой длины.

Ваша строка Array.Clear может несправка, потому что строка logSection не будет собирать мусор. Фактически, когда цикл повторяется, время выполнения будет трудным, поскольку найти, например, 10 Кбайт в куче труднее, чем найти 10 Кбайт.

Вот в чем твоя проблема.Что касается решения, в общем, я бы посоветовал для более ленивого решения.Вам действительно нужны все эти строки в основной памяти?Если да, почему бы вам не прочитать хотя бы из StreamReader вместо загрузки всего в string[] logLines?

1 голос
/ 05 января 2012

Я знаю, что это не ответит на ваш вопрос, но вы можете подумать о том, чтобы не полностью загрузить ваш файл в память.

В вашем случае для вашего файла журнала требуется 300 МБ памяти, но что, если для этого потребуется 2,5 ГБ? Особенно, если результатом является отображение в сетке данных, вы можете использовать вместо этого подкачку и загружать небольшой фрагмент данных из файла каждый раз, когда вам это нужно.

0 голосов
/ 05 января 2012

Я бы посоветовал не загружать все файлы в память и использовать ленивое чтение. Для> = .NET 4 вы можете использовать метод File.ReadLines () для чтения файла.

Когда вы используете ReadLines, вы можете начать перечислять коллекцию строки перед возвратом всей коллекции; Поэтому, когда вы работа с очень большими файлами, ReadLines может быть более эффективной.

foreach (string line in File.ReadLines(@"path-to-a-file"))
{
   // single line processing logic
}
0 голосов
/ 05 января 2012

Первое, что я вижу первым, это то, что вы повторно используете и удваиваете использование памяти, используя такие выражения, как:

File.ReadAllLines(logPath).ToList();

Система сначала прочитает все строки, а затем преобразует ее вСписок, который удваивает использование.

Я бы посоветовал вам прочитать файл через потоковый ридер, используя:

, используя (var sr = new StreamReader (fileName)) {//Получить данные здесь}

Таким образом, память удаляется, как только вы уходите от оператора.

Также Array.Copy собирается использовать больше памяти, поэтому попробуйтесоздать и создать свой объект Desired внутри оператора Using или сделать свой Objects IDisposable таким образом, чтобы GarbageCollector мог сохранить день.

...