【问题标题】:How to optimize memory usage in this algorithm?如何在该算法中优化内存使用?
【发布时间】:2010-01-04 19:44:54
【问题描述】:

我正在开发一个日志解析器,并且我正在读取超过 150MB 的字符串文件。- 这是我的方法,有什么方法可以优化 While 语句中的内容吗?问题是这会消耗大量内存。-我还尝试使用面临相同内存消耗的字符串生成器。-

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#

【问题讨论】:

  • 保留每一行的原因是什么?内存配置文件显示为最昂贵的一个或多个对象?你想要的内存阈值是多少?
  • 你用了多少内存,你觉得合理吗?
  • 我在使用 300MB 内存时停止了进程,尽管我什至不在文件中间 :(
  • 您实际上是将整个文件加载到内存中,并在此过程中增加了开销。 .NET 以 UTF16 将字符串存储在内存中(每个字符使用 2 个字节),而您的文本文件几乎可以肯定使用 1 字节编码。这立即转化为使用的内存量与磁盘大小相比翻了一番。最重要的是,您用于存储数据的跟踪结构中会有一些开销,列表必须维护对每个字符串的引用,并且每个字符串都有一个与之关联的 clr 对象头。

标签: c# string memory-management algorithm


【解决方案1】:

如果您的日志行实际上可以解析为数据行表示,则可以实现内存节约。

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

事件时间: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】:

    尽量让你的算法顺序化。

    如果您不需要通过列表中的索引随机访问行,则使用 IEnumerable 而不是 List 有助于更好地使用内存,同时保持与使用列表相同的语义。

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

    【讨论】:

      【解决方案3】:

      我不确定它是否适合您的项目,但您可以将结果存储在 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);
              }
          }
      }
      

      【讨论】:

      • 嘿,这个不错。让我试试这种方法,我会告诉你的:D 谢谢
      • var text = File.ReadAllText("file.txt");用流式阅读器打开文件只是为了重新构建一个包含所有行的字符串并没有任何帮助
      【解决方案4】:

      内存使用量不断增加,因为您只是将它们添加到 List 中,并且不断增长。如果您想使用更少的内存,您可以做的一件事是将数据写入磁盘,而不是将其保持在范围内。当然,这会大大降低速度。

      另一种选择是在将字符串数据存储到列表时对其进行压缩,然后将其解压缩出来,但我认为这不是一个好方法。

      旁注:

      您需要在流式阅读器周围添加一个 using 块。

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

      【讨论】:

        【解决方案5】:

        考虑这个实现:(我说的是 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.
        

        【讨论】:

        • 这在 C# 环境中是行不通的。 C# 中的字符串与 c 中的 char* 不同。您所说的大部分内容都可以在 C# 中完成,但最终仍然必须将 byte*(最接近 char* 的模拟)转换为 String 对象才能使用,无论如何都会进行复制。
        • 酷。我在我的环境中多次使用这种技术,效果很好。
        【解决方案6】:

        1) 在存储之前压缩字符串(即参见 System.IO.Compression 和 GZipStream)。不过,这可能会降低程序的性能,因为您必须解压缩才能读取每一行。

        2) 删除任何多余的空白字符或常用词。即,如果您能理解日志中的“the, a, of ...”字样,请删除它们。此外,缩短所有常用词(即,将“error”更改为“err”,将“warning”更改为“wrn”)。这会减慢过程中的这一步,但不会影响其余步骤的性能。

        【讨论】:

          【解决方案7】:

          您的原始文件是什么编码?如果它是 ascii,那么仅字符串将占用文件大小的 2 倍,只是为了加载到您的数组中。 C# 字符为 2 个字节,而 C# string 除了字符外,每个字符串还额外增加了 20 个字节。

          在您的情况下,由于它是一个日志文件,您可能可以利用消息中存在大量重复的事实。您很可能可以将传入的行解析为减少内存开销的数据结构。例如,如果您在日志文件中有时间戳,您可以将其转换为 DateTime 值,即8 bytes。即使是1/1/10 的短时间戳也会使字符串的大小增加 12 个字节,而带有时间信息的时间戳会更长。您的日志流中的其他标记可能能够以类似的方式转换为代码或枚举。

          即使您将值保留为字符串,如果您可以将其分解为经常使用的部分,或者删除根本不需要的样板文件,您可能会减少内存使用量。如果有很多常用字符串,你可以Intern他们,不管你有多少只支付1个字符串。

          【讨论】:

            【解决方案8】:

            如果您必须存储原始数据,并且假设您的日志主要是 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));
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-07-19
              • 2012-06-28
              • 1970-01-01
              • 2014-05-05
              • 2014-04-25
              • 1970-01-01
              • 2011-09-06
              • 1970-01-01
              相关资源
              最近更新 更多