2011-10-11 83 views
1

我在我的程序中有一个解析方法,它首先从磁盘读取一个文件,然后解析这些行并为每一行创建一个对象。对于每个文件,以后都会保存包含行中对象的集合。这些文件大约是300MB。 这需要大约2.5-3分钟才能完成。加速多线程

我的问题:如果我将任务分配到一个线程,只是从磁盘读取文件,另一个线程解析和第三个保存集合,我能期待显着的加速吗?或者这可能会减缓这个过程?

现代笔记本硬盘读取300MB常见多长时间?我认为,瓶颈是我的任务中的CPU,因为如果我执行该方法,CPU的一个核心总是处于100%,而磁盘闲置超过半时间。

问候,雨

编辑:

private CANMessage parseLine(String line) 
    { 
     try 
     { 
      CANMessage canMsg = new CANMessage(); 
      int offset = 0; 
      int offset_add = 0; 

      char[] delimiterChars = { ' ', '\t' }; 

      string[] elements = line.Split(delimiterChars); 

      if (!isMessageLine(ref elements)) 
      { 
       return canMsg = null; 
      } 

      offset = getPositionOfFirstWord(ref elements); 

      canMsg.TimeStamp = Double.Parse(elements[offset]); 

      offset += 3; 

      offset_add = getOffsetForShortId(ref elements, ref offset); 

      canMsg.ID = UInt16.Parse(elements[offset], System.Globalization.NumberStyles.HexNumber); 
      offset += 17; // for signs between identifier and data length number 
      canMsg.DataLength = Convert.ToInt16(elements[offset + offset_add]); 
      offset += 1; 
      parseDataBytes(ref elements, ref offset, ref offset_add, ref canMsg); 
      return canMsg; 
     } 
     catch (Exception exp) 
     { 
      MessageBox.Show(line); 
      MessageBox.Show(exp.Message + "\n\n" + exp.StackTrace); 
      return null; 
     } 
    } 
} 

所以这是解析法。它以这种方式工作,但也许你是对的,而且效率低下。我有.NET Framwork 4.0,我在Windows 7上。我有一个Core i7,每个核心都有HypterThreading,所以我只用了大约1/8的CPU。

编辑2:我正在使用Visual Studio 2010专业版。它看起来像用于性能分析的工具在该版本中不可用(根据msdn MSDN Beginners Guide to Performance Profiling)。

EDIT3:我现在更改了代码以使用线程。它看起来像这样:

foreach (string str in checkedListBoxImport.CheckedItems) 
{ 
    toImport.Add(str); 
} 

for(int i = 0; i < toImport.Count; i++) 
{ 
    String newString = new String(toImport.ElementAt(i).ToArray()); 
    Thread t = new Thread(() => importOperation(newString)); 
    t.Start(); 
} 

虽然您在上面看到的解析在importOperation(...)中调用。

使用此代码可以将时间从大约2.5分钟缩短到“仅”40秒。我有一些并发问题需要跟踪,但至少这比以前快得多。

谢谢您的建议。

回答

0

因为我们不知道您的笔记本电脑的年龄有多大,我们也不知道它是销售状态还是旋转状态,所以您不太可能获得笔记本电脑硬盘性能的一致指标。

考虑到你已经完成了一些基本的分析,我肯定CPU是你的瓶颈,因为单线程应用程序不可能使用单个CPU的100%以上。这当然会忽略你的操作系统分裂多核心和其他怪异过程。如果你得到5%的CPU使用率,那很可能是瓶颈在IO。

这就是说,你最好的选择是为你正在处理的每个文件创建一个新的线程任务,并将它发送给池线程管理器。你的线程管理器应该将你正在运行的线程数量限制为你有可用的核心数量,或者如果内存是一个问题(你确实说你最终生成了300MB文件),你可以使用的最大RAM数量。

最后,要回答为什么您不希望为每个操作使用单独的线程的原因,请考虑您已了解的有关性能瓶颈的内容。你是在CPU处理瓶颈,而不是IO。这意味着,如果将应用程序拆分为单独的线程,则读写线程将在等待处理线程完成的大部分时间内耗尽。另外,即使你让它们异步处理,当你的读取线程继续使用你的处理线程无法跟上的数据时,你也会面临真正的内存不足的风险。

因此,小心不要立即启动每个线程,而是让它们由某种形式的阻塞队列管理。否则,由于在上下文切换中花费的时间多于处理时间,因此可能会导致系统放慢搜索速度。这当然假设你不首先崩溃。

0

目前还不清楚你有多少这些300MB文件。一个300MB的文件需要大约5或6秒才能在我的上网本上读取,并进行快速测试。它确实听起来像你是CPU限制。

线程可能会有所帮助,虽然它可能会使事情显着复杂化。你还应该剖析你当前的代码 - 很可能你只是低效地解析。 (例如,如果你使用C#或Java和你在一个环路连接字符串,这是经常演出“疑难杂症”,它可以很容易地纠正。)

如果一个多办选择那么为了避免颠倒磁盘,您可能希望让一个线程将每个文件读入内存(一次一个),然后将该数据传递给解析线程池。当然,这假设你也有足够的内存来这样做。

如果您可以指定平台并提供解析代码,我们可以帮助您优化它。目前我们可以真正说的是,这听起来像是你的CPU绑定。

0

鉴于您认为这是一个CPU绑定任务,您应该看到吞吐量随着单独的IO线程的总体增加(否则,您的唯一处理线程会在磁盘读取/写入操作期间阻止等待IO)。

最近我有一个类似的问题,通过运行单独的IO线程(以及足够的计算线程来加载所有CPU内核),看到了显着的净改善。

你没有说明你的平台,但我使用了Task Parallel Library和一个BlockingCollection作为我的.NET解决方案,实现几乎是微不足道的。 MSDN提供了一个很好的例子。

UPDATE:

由于乔恩指出,相较于花费计算,因此,尽管你可以预期的改善,最好的时间可使用分析和提高计算本身的时间花费在IO的时间大概是小。使用多个线程进行计算会显着加快。

+0

如果这个任务是CPU绑定的话,我认为有一定的空间用于改进代码。 :) – bzlm

0

长只有300 MB是坏的。

有不同的事情可能会影响性能以及情况,但通常情况下,它读取硬盘仍然可能是最大的瓶颈,除非你在解析过程中发生了一些激烈的事情,这在这里似乎是这样,因为从硬盘读取300MB只需要几秒钟(除非它可能被破坏)。

如果你在解析中有一些低效的算法,那么挑选或提出一个更好的算法可能会更有益。如果你绝对需要这种算法,并且没有可用的算法改进,那么听起来你可能会陷入困境。

此外,不要尝试用多线程同时读取和写入多线程,您可能会减慢搜索速度。

0

嗯.. 300MB的线必须分成许多CAN消息对象 - 讨厌!我怀疑这个技巧可能是为了避免在读取和写入操作之间出现过多的磁盘颠簸,从而消除消息组合。如果我这样做是一个'新鲜'的要求,(当然,以我20/20的后见之明,知道CPU会成为问题),我可能只用一个线程来阅读,一个用于写入磁盘,并且最初至少为消息对象组件创建一个线程。使用多个线程进行消息组装意味着在处理后重新排序对象以避免输出文件被乱序写入的复杂性。

我会定义一个不错的磁盘友好大小的块级行和消息对象数组实例,比如说它们的1024个,并且在启动时创建一个块,并将它们推送到存储队列中。这样控制和限制内存的使用,大大减少了new/dispose/malloc/free,(看起来你现在有很多这个东西!),由于只执行大的r/w操作,提高了磁盘读写操作的效率(除了最后一个块,通常只有部分被填充),它提供了固有的流控制(读线程不能'逃跑',因为池将用完块,读线程将阻塞直到写入线程返回一些块),并禁止过度的上下文切换,因为只处理大块。

读线程打开文件,从队列中获取块,读取磁盘,解析为行并将行移入块中。然后它将整个块排队到处理线程并循环以从池中获取另一个块。可能的是,读取线程可以在开始或闲置时在其自己的输入队列中等待包含读/写文件规格的消息类实例。 write filespec可以通过块的字段传播,因此提供写入线程将获得所需的所有内容。大块。这使得一个很好的子系统能够将文件规格排队,并且它将在没有任何进一步干预的情况下处理它们。

处理线程从其输入队列中获取块并将线路拆分成块中的消息对象,然后将完成的整个块排队到写入线程。

写入线程将消息对象写入输出文件,然后将该块重新指定到存储池队列以供读取线程重新使用。

所有的队列应该阻塞生产者 - 消费者队列。

线程子系统的一个问题是完成通知。当写入线程写入文件的最后一个块时,可能需要执行某些操作。我可能会用最后一个块作为参数来引发一个事件,以便事件处理程序知道哪个文件已经被完全写入。我可能会有类似的错误通知。

如果这还不够快,你可以尝试:

1)确保使用互斥的读取和写入线程不能支持其他的块,耙地时preemepted。如果你的块很大,这可能没有太大的区别。

2)使用多个处理线程。如果你这样做,块可能会到达写入线程'无序'。您可能需要一个本地列表,并且可能需要某些序列号才能确保磁盘写入的顺序正确。

运气好,你来什么设计了..

RGDS, 马丁

+0

为什么这是“讨厌”?我别无选择。我从另一个软件包中获取文本文件作为输出。有没有更好的方法来解析它,比我这样做,或者你是什么意思与“讨厌”? – JoeFox