Как оптимизировать использование памяти в этом алгоритме? - PullRequest
1 голос
/ 04 января 2010

Я разрабатываю синтаксический анализатор журналов и читаю файлы строк размером более 150 МБ. - Это мой подход. Есть ли способ оптимизировать то, что содержится в операторе while? Проблема в том, что она потребляет много памяти.- Я также пытался с помощью string Builder, столкнувшегося с таким же объемом памяти .-

private void ReadLogInThread()
        {
            string lineOfLog = string.Empty;

            try
            {
                StreamReader logFile = new StreamReader(myLog.logFileLocation);
                InformationUnit infoUnit = new InformationUnit();

                infoUnit.LogCompleteSize = myLog.logFileSize;

                while ((lineOfLog = logFile.ReadLine()) != null)
                {
                    myLog.transformedLog.Add(lineOfLog); //list<string>
                    myLog.logNumberLines++;

                    infoUnit.CurrentNumberOfLine = myLog.logNumberLines;
                    infoUnit.CurrentLine = lineOfLog;
                    infoUnit.CurrentSizeRead += lineOfLog.Length;


                    if (onLineRead != null)
                        onLineRead(infoUnit);
                }
            }
            catch { throw; }
        }

Заранее спасибо!

EXTRA: Я сохраняю каждую строку, потому что после прочтения журнала мне нужно будет проверить некоторую информацию в каждой сохраненной строке. Язык C #

Ответы [ 8 ]

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

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

Вот типичная строка журнала, о которой я могу подумать:

Событие в: 2019/01/05: 0: 24: 32.435, Причина: операция, Вид: DataStoreOperation, Состояние операции: успех

Эта строка занимает 200 байт в памяти. В то же время следующее представление занимает всего 16 байтов:

Enum LogReason { Operation, Error, Warning };
Enum EventKind short { DataStoreOperation, DataReadOperation };
Enum OperationStatus short { Success, Failed };

LogRow
{
  DateTime EventTime;
  LogReason Reason;
  EventKind Kind;
  OperationStatus Status;
}

Другая возможность оптимизации - просто анализ строки для массива строковых токенов, таким образом, вы можете использовать интернирование строк. Например, если слово «DataStoreOperation» занимает 36 байтов и если в файле содержится 1000000 записей, экономия составляет (18 * 2–4) * 1000000 = 32 000 000 байтов.

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

Попробуйте сделать ваш алгоритм последовательным.

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

IEnumerable<string> ReadLines()
{
  // ...
  while ((lineOfLog = logFile.ReadLine()) != null)
  {
    yield return lineOfLog;
  }
}
//...
foreach( var line in ReadLines() )
{
  ProcessLine(line);
}
1 голос
/ 04 января 2010

Я не уверен, подходит ли он вашему проекту, но вы можете сохранить результат в StringBuilder вместо списка строк.

Например, этот процесс на моей машине занимает 250 МБ памяти после загрузки (размер файла 50 МБ):

static void Main(string[] args)
{
    using (StreamReader streamReader = File.OpenText("file.txt"))
    {
        var list = new List<string>();
        string line;
        while (( line=streamReader.ReadLine())!=null)
        {
            list.Add(line);
        }
    }
}

С другой стороны, этот процесс кода займет всего 100 МБ:

static void Main(string[] args)
{
    var stringBuilder = new StringBuilder();
    using (StreamReader streamReader = File.OpenText("file.txt"))
    {
        string line;
        while (( line=streamReader.ReadLine())!=null)
        {
            stringBuilder.AppendLine(line);
        }
    }
}
0 голосов
/ 05 января 2010

Если вы должны хранить необработанные данные и предполагать, что ваши журналы в основном ASCII, то вы можете сэкономить некоторую память, храня байты UTF8 внутри. Внутренние строки имеют формат UTF16, поэтому вы сохраняете дополнительный байт для каждого символа. Таким образом, переключаясь на UTF8, вы сокращаете использование памяти вдвое (не считая затрат на класс, что все еще важно). Затем вы можете конвертировать обратно в обычные строки по мере необходимости.

static void Main(string[] args)
{
    List<Byte[]> strings = new List<byte[]>();

    using (TextReader tr = new StreamReader(@"C:\test.log"))
    {
        string s = tr.ReadLine();
        while (s != null)
        {
            strings.Add(Encoding.Convert(Encoding.Unicode, Encoding.UTF8, Encoding.Unicode.GetBytes(s)));
            s = tr.ReadLine();
        }
    }

    // Get strings back
    foreach( var str in strings)
    {
        Console.WriteLine(Encoding.UTF8.GetString(str));
    }
}
0 голосов
/ 05 января 2010

Какая кодировка вашего исходного файла? Если это ascii, то только для одной строки будут загружены в два раза больше размера файла, чтобы загрузить их в ваш массив. Символ C # составляет 2 байта, а C # string добавляет дополнительно 20 байтов на строку в дополнение к символам.

В вашем случае, поскольку это файл журнала, вы, вероятно, можете использовать тот факт, что в сообщениях много повторений. Скорее всего, вы можете разобрать входящую строку в структуру данных, которая уменьшает накладные расходы памяти. Например, если у вас есть временная метка в файле журнала, вы можете преобразовать ее в значение DateTime, которое составляет 8 байт . Даже короткая метка времени 1/1/10 добавит 12 байтов к размеру строки, а метка времени с информацией о времени будет еще длиннее. Другие токены в вашем потоке журналов могут быть превращены в код или перечисление аналогичным образом.

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

0 голосов
/ 04 января 2010

1) Сжатие строк перед сохранением их (т. Е. См. System.IO.Compression и GZipStream). Это, вероятно, снизит производительность вашей программы, поскольку вам придется распаковывать, чтобы прочитать каждую строку.

2) Удалите все лишние пробелы или обычные слова, без которых вы можете обойтись. т. е. если вы можете понять, что в журнале говорится со словами «a, of, ...», удалите их. Кроме того, сократите все распространенные слова (то есть замените «error» на «err» и «warning» на «wrn»). Это замедлит этот шаг процесса, но не повлияет на производительность остальных.

0 голосов
/ 04 января 2010

Рассмотрим эту реализацию: (я говорю на c / c ++, подставьте c # при необходимости)

Use fseek/ftell to find the size of the file.

Use malloc to allocate a chunk of memory the size of the file + 1;
Set that last byte to '\0' to terminate the string.

Use fread to read the entire file into the memory buffer.
You now have char * which holds the contents of the file as a 
string.

Create a vector of const char * to hold pointers to the positions 
in memory where each line can be found.   Initialize the first element 
of the vector to the first byte of the memory buffer.

Find the carriage control characters (probably \r\n)   Replace the 
\r by \0 to make the line a string.   Increment past the \n.  
This new pointer location is pushed back onto the vector.

Repeat the above until all of the lines in the file have been NUL 
terminated, and are pointed to by elements in the vector.

Iterate though the vector as needed to investigate the contents of 
each line, in your business specific way.

When you are done, close the file, free the memory,  and continue 
happily along your way.
0 голосов
/ 04 января 2010

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

Другой вариант - сжимать строковые данные, когда вы сохраняете их в свой список, и распаковывать их, но я не думаю, что это хороший метод.

Примечание:

Вам необходимо добавить блок использования вокруг вашего потокового считывателя.

using (StreamReader logFile = new StreamReader(myLog.logFileLocation))
...