2015-09-30 45 views
3

我正在将文本文件(.itf)与位于文件夹中的某些逻辑进行合并。当我将它编译为32位(控制台应用程序,.Net 4.6)时,一切正常,但如果文件夹中有大量数据,则会得到outofmemory异常。将其编译为64位可以解决这个问题,但与32位进程相比,它运行速度非常慢(超过15倍)。用64位进程读取文本文件非常慢

我试过BufferedStreamReadAllLines,但两者表现都很差。分析器告诉我这些方法占用了99%的时间。我不知道是问题是...

下面的代码:

private static void readData(Dictionary<string, Topic> topics) 
{ 
    foreach (string file in Directory.EnumerateFiles(Path, "*.itf")) 
    { 
     Topic currentTopic = null; 
     Table currentTable = null; 
     Object currentObject = null; 
     using (var fs = File.Open(file, FileMode.Open)) 
     { 
      using (var bs = new BufferedStream(fs)) 
      { 
       using (var sr = new StreamReader(bs, Encoding.Default)) 
       { 
        string line; 
        while ((line = sr.ReadLine()) != null) 
        { 
         if (line.IndexOf("ETOP") > -1) 
         { 
          currentTopic = null; 
         } 
         else if (line.IndexOf("ETAB") > -1) 
         { 
          currentTable = null; 
         } 
         else if (line.IndexOf("ELIN") > -1) 
         { 
          currentObject = null; 
         } 
         else if (line.IndexOf("MTID") > -1) 
         { 
          MTID = line.Replace("MTID ", ""); 
         } 
         else if (line.IndexOf("MODL") > -1) 
         { 
          MODL = line.Replace("MODL ", ""); 
         } 
         else if (line.IndexOf("TOPI") > -1) 
         { 
          var name = line.Replace("TOPI ", ""); 
          if (topics.ContainsKey(name)) 
          { 
           currentTopic = topics[name]; 
          } 
          else 
          { 
           var topic = new Topic(name); 
           currentTopic = topic; 
           topics.Add(name, topic); 
          } 
         } 
         else if (line.IndexOf("TABL") > -1) 
         { 
          var name = line.Replace("TABL ", ""); 
          if (currentTopic.Tables.ContainsKey(name)) 
          { 
           currentTable = currentTopic.Tables[name]; 
          } 
          else 
          { 
           var table = new Table(name); 
           currentTable = table; 
           currentTopic.Tables.Add(name, table); 
          } 
         } 
         else if (line.IndexOf("OBJE") > -1) 
         { 
          if (currentTable.Name != "Metadata" || currentTable.Objects.Count == 0) 
          { 
           var shortLine = line.Replace("OBJE ", ""); 
           var obje = new Object(shortLine.Substring(shortLine.IndexOf(" "))); 
           currentObject = obje; 
           currentTable.Objects.Add(obje); 
          } 
         } 
         else if (currentTopic != null && currentTable != null && currentObject != null) 
         { 
          currentObject.Data.Add(line); 
         } 
        } 
       } 
      } 
     } 
    } 
} 
+0

那么Profiler所说的ReadAllLines在哪里放慢速度?另外,你的瓶颈很可能是由于'string.IndexOf'。提示:投资创建一个合适的词法分析器/解析器。 – leppie

+0

我想知道是否字符串分配的数量(所有这些调用'.Replace'创建新字符串)是罪魁祸首 - 一个真正的分析器可能会告诉,但我想知道是否一个机制,将整个文件作为流并读取字符没有任何修改/操作行的字符将是更好的解决方案。 –

+0

代码示例显示了“BufferedStream”版本。我也有一个'ReadAllLines'。在32位分析器中确实表示'Replace'和'IndexOf'方法消耗大量时间。不过,我想知道为什么64位版本要慢得多。 – Chris

回答

1

一些提示:

  • 为什么使用File.Open,然后BufferedStream然后StreamReader时 你可以用StreamReader来完成这项工作,该工作被缓冲了吗?
  • 你应该对你的条件进行重新排序,使其发生的次数更多。
  • 请考虑读取所有行,然后使用Parallel.ForEach
+0

thx为您的提示,我实现了他们。虽然并行性在我的情况下不起作用,但由于内容的模型,我必须依次解析它们。 – Chris

1

我可以解决这个问题。似乎.Net编译器中存在一个错误。删除VS2015中的代码优化复选框会导致性能提升。现在,它运行的性能与32位版本相似。我的最终版本有一些优化:

private static void readData(ref Dictionary<string, Topic> topics) 
    { 
     Regex rgxOBJE = new Regex("OBJE [0-9]+ ", RegexOptions.IgnoreCase | RegexOptions.Compiled); 
     Regex rgxTABL = new Regex("TABL ", RegexOptions.IgnoreCase | RegexOptions.Compiled); 
     Regex rgxTOPI = new Regex("TOPI ", RegexOptions.IgnoreCase | RegexOptions.Compiled); 
     Regex rgxMTID = new Regex("MTID ", RegexOptions.IgnoreCase | RegexOptions.Compiled); 
     Regex rgxMODL = new Regex("MODL ", RegexOptions.IgnoreCase | RegexOptions.Compiled); 
     foreach (string file in Directory.EnumerateFiles(Path, "*.itf")) 
     { 
      if (file.IndexOf("itf_merger_result") == -1) 
      { 
       Topic currentTopic = null; 
       Table currentTable = null; 
       Object currentObject = null; 
       using (var sr = new StreamReader(file, Encoding.Default)) 
       { 
        Stopwatch sw = new Stopwatch(); 
        sw.Start(); 
        Console.WriteLine(file + " read, parsing ..."); 
        string line; 
        while ((line = sr.ReadLine()) != null) 
        { 
         if (line.IndexOf("OBJE") > -1) 
         { 
          if (currentTable.Name != "Metadata" || currentTable.Objects.Count == 0) 
          { 
           var obje = new Object(rgxOBJE.Replace(line, "")); 
           currentObject = obje; 
           currentTable.Objects.Add(obje); 
          } 
         } 
         else if (line.IndexOf("TABL") > -1) 
         { 
          var name = rgxTABL.Replace(line, ""); 
          if (currentTopic.Tables.ContainsKey(name)) 
          { 
           currentTable = currentTopic.Tables[name]; 
          } 
          else 
          { 
           var table = new Table(name); 
           currentTable = table; 
           currentTopic.Tables.Add(name, table); 
          } 
         } 
         else if (line.IndexOf("TOPI") > -1) 
         { 
          var name = rgxTOPI.Replace(line, ""); 
          if (topics.ContainsKey(name)) 
          { 
           currentTopic = topics[name]; 
          } 
          else 
          { 
           var topic = new Topic(name); 
           currentTopic = topic; 
           topics.Add(name, topic); 
          } 
         } 
         else if (line.IndexOf("ETOP") > -1) 
         { 
          currentTopic = null; 
         } 
         else if (line.IndexOf("ETAB") > -1) 
         { 
          currentTable = null; 
         } 
         else if (line.IndexOf("ELIN") > -1) 
         { 
          currentObject = null; 
         } 
         else if (currentTopic != null && currentTable != null && currentObject != null) 
         { 
          currentObject.Data.Add(line); 
         } 
         else if (line.IndexOf("MTID") > -1) 
         { 
          MTID = rgxMTID.Replace(line, ""); 
         } 
         else if (line.IndexOf("MODL") > -1) 
         { 
          MODL = rgxMODL.Replace(line, ""); 
         } 
        } 
        sw.Stop(); 
        Console.WriteLine(file + " parsed in {0}s", sw.ElapsedMilliseconds/1000.0); 
       } 
      } 
     } 
    } 
+0

另一个RyuJIT错误? – leppie

4

与你的程序的最大问题是,当你让它在64位模式下运行,那么它可以读取更多的文件。这很好,一个64位进程的地址空间比32位进程多一千倍,用完它不太可能。

但是,你没有得到更多的内存。

工作中“没有免费午餐”的普遍原则。有足够的内存在这样的程序中很重要。首先,它由文件系统缓存使用。神奇的操作系统功能,使它看起来像就像从磁盘读取文件是非常便宜。这完全不是你在程序中可以做的最慢的事情之一,但它非常善于隐藏它。当你运行你的程序不止一次时,你会调用它。第二次以后,你根本不会从磁盘读取数据。这是一个非常危险的特性,当你测试你的程序时很难避免,你会得到很不切实际的关于它的效率的假设。

64位进程的问题是它很容易使文件系统缓存失效。由于您可以读取更多文件,因此压倒了缓存。并删除旧的文件数据。现在你第二次运行你的程序时,它不会再快了。您读取的文件将而不是不再在缓存中,但必须从磁盘读取。您现在将看到您的程序的perf,它将在生产中表现的方式。这是一件好事,尽管你不喜欢它:)

RAM的次要问题是较小的一个,如果你分配大量的内存来存储文件数据,那么你会强制操作系统找到RAM来存储它。这可能会导致很多硬页面错误,当它必须取消映射另一个进程使用的内存或您的内存时,才会释放所需的RAM。一个称为“颠簸”的通用问题。页面故障是你可以在任务管理器中看到的东西,使用视图>选择列来添加它。

鉴于文件系统缓存是缓存的最可能来源,您可以执行一个简单的测试,即重新启动您的计算机,以确保缓存不具有任何文件数据,然后运行32位缓存, bit版本。预测它也会很慢,BufferedStream和ReadAllLines是瓶颈。就像他们应该。

最后一点需要注意的是,即使程序与模式不匹配,您仍然无法对.NET 4.6性能问题做出有力的假设。直到this very nasty bug得到修复。

0

删除代码优化复选框通常应导致性能下降,而不是加速。 VS 2015产品可能存在问题。请为您的程序提供一个独立的数据恢复案例,其中包含一个输入集,用于演示性能问题和报告:http://connect.microsoft.com/

相关问题