写入文件C#的性能

我的情况概述:

我的任务是从文件中读取字符串,并将它们重新格式化为更有用的格式。 重新格式化输入后,我必须将其写入输出文件。

以下是必须完成的示例。 文件行示例:

ANO=2010;CPF=17834368168;YEARS=2010;2009;2008;2007;2006 178343681682010Sua declaração não consta na base de dados da Receita Federal05/01/2012178343681682009Sua declaração não consta na base de dados da Receita Federal05/01/2012178343681682008Sua declaração não consta na base de dados da Receita Federal05/01/2012178343681682007Sua declaração consta como Pedido de Regularização(PR), na base de dados da Secretaria da Receita Federal do Brasil05/01/2012178343681682006Sua declaração não consta na base de dados da Receita Federal05/01/2012TRUE 

此输入文件在每行上有两个重要信息: CPF ,即我将使用的文档编号,以及XML文件(表示在数据库上返回文档的查询)。

我必须达到的目标:

这种old format每个Document都有一个XML其中包含所有年份(2006年至2010年)的查询返回值。 重新格式化后,每个输入行将转换为5个输出行:

 CPF=17834368168;YEARS=2010; 178343681682010Sua declaração não consta na base de dados da Receita Federal05/01/2012TRUE CPF=17834368168;YEARS=2009; 178343681682009Sua declaração não consta na base de dados da Receita Federal05/01/2012TRUE CPF=17834368168;YEARS=2008; 178343681682008Sua declaração não consta na base de dados da Receita Federal05/01/2012TRUE CPF=17834368168;YEARS=2007; 178343681682007Sua declaração consta como Pedido de Regularização(PR), na base de dados da Secretaria da Receita Federal do Brasil05/01/2012TRUE CPF=17834368168;YEARS=2006; 178343681682006Sua declaração não consta na base de dados da Receita Federal05/01/2012TRUE 

一行,包含有关该文档的每年信息。 所以基本上,输出文件比输入文件长5倍

绩效问题:

每个文件有400,000行,我有133个文件要处理。

目前,这是我的应用程序的流程:

  1. 打开一个文件
  2. 读一行
  3. 将其解析为新格式
  4. 将该行写入输出文件
  5. 转到2直到没有左线
  6. Goto1直到没有左文件

每个输入文件大约700MB,并且它将永远读取文件并将它们的转换版本写入另一个文件。 400KB的文件需要大约30秒才能完成此过程。

额外的信息:

我的机器运行在Intel i5处理器上,内存为8GB。

我没有实例化大量的对象来避免mem。 泄漏,我在输入文件打开时using子句。

我该怎么做才能让它跑得更快?

我不知道你的代码是什么样的,但是这里有一个例子,我的盒子(不可否认,有一个SSD和一个i7,但……)在大约50ms内处理一个400K的文件。

我甚至都没想过要优化它 – 我用最干净的方式写出来。 (请注意,它都是懒惰的评估; File.ReadLinesFile.WriteAllLines负责打开和关闭文件。)

 using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; class Test { public static void Main() { Stopwatch stopwatch = Stopwatch.StartNew(); var lines = from line in File.ReadLines("input.txt") let cpf = ParseCpf(line) let xml = ParseXml(line) from year in ParseYears(line) select cpf + year + xml; File.WriteAllLines("output.txt", lines); stopwatch.Stop(); Console.WriteLine("Completed in {0}ms", stopwatch.ElapsedMilliseconds); } // Returns the CPF, in the form "CPF=xxxxxx;" static string ParseCpf(string line) { int start = line.IndexOf("CPF="); int end = line.IndexOf(";", start); // TODO: Validation return line.Substring(start, end + 1 - start); } // Returns a sequence of year values, in the form "YEAR=2010;" static IEnumerable ParseYears(string line) { // First year. int start = line.IndexOf("YEARS=") + 6; int end = line.IndexOf(" ", start); // TODO: Validation string years = line.Substring(start, end - start); foreach (string year in years.Split(';')) { yield return "YEARS=" + year + ";"; } } // Returns all the XML from the leading space onwards static string ParseXml(string line) { int start = line.IndexOf("  

这看起来像流水线的一个很好的候选人。

基本思想是拥有3个并发任务 ,一个用于管道中的每个“阶段”,通过队列相互通信( BlockingCollection ):

  1. 第一个任务逐行读取输入文件并将读取行放入队列。
  2. 第二个任务从队列中获取行,格式化它们并将结果放入另一个队列。
  3. 第三个任务从第二个队列获取格式化结果,并将它们写入结果文件。

理想情况下,任务1不应等到任务2完成后再转到下一个文件。

你甚至可以疯狂地将每个单独文件的管道放入一个单独的并行任务中,但是这会让你的硬盘处于糟糕的状态,这可能会让你受伤更多。 另一方面,对于SSD而言,实际上这可能是合理的 – 无论如何在做出决定之前进行测量。

—编辑—

使用John Skeet的单线程实现作为基础,这是流水线版本的样子(工作示例):

 class Test { struct Queue2Element { public string CPF; public List Years; public string XML; } public static void Main() { Stopwatch stopwatch = Stopwatch.StartNew(); var queue1 = new BlockingCollection(); var task1 = new Task( () => { foreach (var line in File.ReadLines("input.txt")) queue1.Add(line); queue1.CompleteAdding(); } ); var queue2 = new BlockingCollection(); var task2 = new Task( () => { foreach (var line in queue1.GetConsumingEnumerable()) queue2.Add( new Queue2Element { CPF = ParseCpf(line), XML = ParseXml(line), Years = ParseYears(line).ToList() } ); queue2.CompleteAdding(); } ); var task3 = new Task( () => { var lines = from element in queue2.GetConsumingEnumerable() from year in element.Years select element.CPF + year + element.XML; File.WriteAllLines("output.txt", lines); } ); task1.Start(); task2.Start(); task3.Start(); Task.WaitAll(task1, task2, task3); stopwatch.Stop(); Console.WriteLine("Completed in {0}ms", stopwatch.ElapsedMilliseconds); } // Returns the CPF, in the form "CPF=xxxxxx;" static string ParseCpf(string line) { int start = line.IndexOf("CPF="); int end = line.IndexOf(";", start); // TODO: Validation return line.Substring(start, end + 1 - start); } // Returns a sequence of year values, in the form "YEAR=2010;" static IEnumerable ParseYears(string line) { // First year. int start = line.IndexOf("YEARS=") + 6; int end = line.IndexOf(" ", start); // TODO: Validation string years = line.Substring(start, end - start); foreach (string year in years.Split(';')) { yield return "YEARS=" + year + ";"; } } // Returns all the XML from the leading space onwards static string ParseXml(string line) { int start = line.IndexOf("  

事实certificate,上面的并行版本仅略高于串行版本。 显然,任务的I / O限制比其他任何东西都多,所以流水线技术没有多大帮助。 如果你增加处理量(例如添加一个强大的validation),这可能会改变这种情况,有利于并行性,但是现在你可能最好只关注串行改进(正如John Skeet自己指出的那样,代码不是尽可能快)。

(另外,我测试了缓存文件 - 我想知道是否有办法清除Windows文件缓存并查看深度2的硬件I / O队列是否允许硬盘优化磁头移动与I / O深度1相比串口版。)

绝对不是IO问题 – 检查您的处理,使用分析器知道所有时间片的持有者和位置。

显示您的处理代码,可能是您使用了一些低效的字符串操作…

你可以马上做几件基本的事情……

  1. 运行多个线程,以便同时处理多个文件。
  2. 使用StringBuilder或StringBuffer而不是string concat
  3. 如果您使用XmlDocument来解析XML,请将其替换为XmlTextReader和XmlTextWriter
  4. 如果您不需要它,请不要将字符串转换为数字并返回字符串
  5. 删除所有不必要的字符串操 例如,不要在下一行做str.Contains只做str.IndexOf。 而是调用str.IndexOf将结果存储在本地var中并检查是否> 0。

自己运行算法的不同部分并测量时间。 首先逐行读取整个文件并测量。 将相同的行写回新文件并进行测量。 从xml中拆分前缀信息并进行测量。 解析xml ….这样你就会知道瓶颈是什么,并专注于那个部分。