簡體   English   中英

如何在此算法中優化內存使用?

[英]How to optimize memory usage in this algorithm?

我正在開發一個日志解析器,並且正在讀取150MB以上的字符串文件。-這是我的方法,是否有任何方法可以優化While語句中的內容? 問題在於這會占用大量內存。-我也嘗試過使用一個面對相同內存消耗的stringbuilder。-

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; }
        }

提前致謝!

額外:我保存每行,因為在讀取日志后,我將需要檢查每行存儲的信息。-語言為C#

如果您的日志行實際上可以解析為數據行表示形式,則可以實現內存節省。

這是我能想到的典型日志行:

事件在: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 = 32000000字節。

嘗試使算法順序化。

如果您不需要通過列表中的索引隨機訪問行,則使用IEnumerable而不是List有助於更好地利用內存,同時保持與使用list相同的語義。

IEnumerable<string> ReadLines()
{
  // ...
  while ((lineOfLog = logFile.ReadLine()) != null)
  {
    yield return lineOfLog;
  }
}
//...
foreach( var line in ReadLines() )
{
  ProcessLine(line);
}

我不確定它是否適合您的項目,但是您可以將結果存儲在StringBuilder中,而不是字符串列表中。

例如,加載后我的計算機上的此過程占用250MB內存(文件為50MB):

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

另一方面,此代碼過程將僅占用100MB:

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

內存使用率一直在上升,因為您只是將它們添加到List <string>,並且不斷增長。 如果要使用更少的內存,您可以做的一件事是將數據寫入磁盤,而不是將其保留在范圍內。 當然,這將極大地導致速度降低。

另一種選擇是在將字符串數據存儲到列表中時對其進行壓縮,然后將其解壓縮,但我認為這不是一個好方法。

邊注:

您需要在流閱讀器周圍添加一個using塊。

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

考慮以下實現:(我在講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.

1)在存儲字符串之前先對其進行壓縮(例如,請參閱System.IO.Compression和GZipStream)。 盡管這可能會破壞程序的性能,因為您必須解壓縮才能讀取每一行。

2)刪除多余的空白字符或常用字。 即,如果您可以理解日志中用“ the,a,of ...”字樣說的話,請將其刪除。 另外,請縮短所有常用詞(即,將“錯誤”更改為“錯誤”,將“警告”更改為“錯誤”)。 這將減慢此過程的步驟,但不會影響其余部分的性能。

您的原始文件編碼是什么? 如果是ascii,那么僅字符串就將占用文件大小的2倍,才可以加載到數組中。 AC#字符是2個字節,而C# 字符串除了這些字符外,每個字符串還增加了20個字節。

在您的情況下,由於它是一個日志文件,因此您可以利用以下事實:消息中有很多重復。 您很可能可以將輸入行解析為數據結構,從而減少內存開銷。 例如,如果日志文件中有時間戳,則可以將其轉換為DateTime值,該值是8 bytes 即使是1/1/10的短時間戳,字符串的大小也會增加12個字節,並且帶有時間信息的時間戳會更長。 日志流中的其他令牌可能可以通過類似的方式轉換為代碼或枚舉。

即使您將值保留為字符串,如果您可以將其分解為經常使用的部分,或者刪除根本不需要的樣板,則可能會減少內存使用量。 如果有很多常見的字符串,您可以實習它們,無論您有多少,都只需支付1個字符串的費用。

如果必須存儲原始數據,並假設日志大部分為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));
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM