2011-10-11 82 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