2014-11-08 50 views
1

我在我的基於C#的WPF應用程序中有一個文件列表:List<string> Files如何使這些IO讀取並行和高性能

Files包含〜1,000,000個獨特的文件路徑。

我在我的應用程序上運行了一個分析器。當我嘗試執行並行操作時,由於IO限制,它是非常滯後的。它甚至落後於我的UI線程,儘管沒有調度員要他們(注意是兩行我已經標記爲關閉):

Files.AsParallel().ForAll(x => 
{ 
    char[] buffer = new char[0x100000]; 
    using (FileStream stream = new FileStream(x, FileMode.Open, FileAccess.Read)) // EXTREMELY SLOW 
    using (StreamReader reader = new StreamReader(stream, true)) 
    { 
     while (true) 
     { 
      int bytesRead = reader.Read(buffer, 0, buffer.Length); // EXTREMELY SLOW 
      if (bytesRead <= 0) 
      { 
       break; 
      } 
     } 
    } 
} 

的這兩行代碼繼續我的整個輪廓測試運行的〜70%。我希望實現IO的最大並行化,同時保持性能,使其不會完全癱瘓我的應用程序的UI。沒有什麼影響我的表現。證明:使用Files.ForEach不會削弱我的用戶界面,並且WithDegreeOfParallelism也可以提供幫助(但是,我正在編寫一個應用程序,該應用程序應該用於任何PC,因此我不能假定此計算具有特定的並行度);另外,我所在的個人電腦上有一個固態硬盤。我搜索了StackOverflow,並找到了使用異步IO讀取方法的鏈接。不過,我不確定他們在這種情況下是如何應用的。也許有人可以擺脫一些光明?也;你如何調整一個新的FileStream的構造函數時間;這甚至有可能嗎?

編輯:好吧,這裏有一些奇怪的東西,我已經注意到了......當我在使用AsParallel的同時將Read讀取爲ReadAsync時,UI不會被壓壞。簡單地等待由ReadAsync創建的任務完成後,會導致我的UI線程保持某種程度的可用性。我認爲這樣做是爲了在不破壞現有線程的情況下維持最佳的磁盤使用率而在此方法中完成的某種異步調度。在那個筆記上,操作系統有沒有機會爭奪現有的線程來執行IO,比如我的應用程序的UI線程? 我真的不明白爲什麼它減慢我的UI線程。操作系統調度是從我的線程上的IO或其他什麼工作?他們是否對CLR做了些什麼來吃掉沒有明確地使用Thread.BeginThreadAffinity之類的線?記憶不是問題;我正在看任務管理器,有很多。

+0

定義「非常」緩慢。您知道磁盤讀取速度比從RAM讀取速度慢100,000,000倍嗎? – aquinas 2014-11-08 07:32:24

+0

你只是檢查文件是否存在?如果你是我會寫我自己的搜索。將所有文件名放入列表中。然後從基本目錄開始,並通過所有目錄進行遞歸搜索。找到文件時,將其從列表中刪除。然後,您可以返回尚未從列表中刪除的文件列表。如果你不嘗試這個,你應該更多地解釋你正在努力完成的事情。 – deathismyfriend 2014-11-08 07:33:18

+0

@deathismyfriend不;我正在閱讀內容。我有一個關於「存在」問題的帖子:http://stackoverflow.com/questions/26321366/fastest-way-to-get-directory-data-in-net雖然這很容易,但速度非常快,但是沒有,這不是我想要/需要的。 – Alexandru 2014-11-08 07:39:56

回答

0

文件訪問本質上不平行。如果您在閱讀其他文檔的同時對待某些文件,則只能從並行性中受益。平行等待磁盤是沒有意義的。

而不是等待100 000次1毫秒磁盤訪問,您編程等待一次100 000毫秒= 100秒。

+0

但是,有沒有優化這些閱讀時間的調度方法?我將用異步方法嘗試一些棘手的問題。我的意思是,理想情況下,必須有一些方法來優化讀取時間。必須有,必須內置。 – Alexandru 2014-11-08 07:44:07

+0

@Alexandru:同時進行多個讀取操作有一些理論上的好處,因爲它允許Windows和磁盤控制器以更有效的方式安排讀取。但a)改進不會太戲劇性,b)如果你太過分了,你會因爭奪過多和表現不佳而結束。您應該從基準測量開始,您可以比較硬件給出的理論最大性能。否則,無法回答你所看到的是否「太慢」。 – 2014-11-08 07:47:57

+0

我同意彼得。這實際上就是我的意思:磁盤訪問有一個不可壓縮的時間,這是由於物理限制以及操作系統和控制器的工作方式。 – 2014-11-08 07:53:52

0

不幸的是,這是一個模糊的問題,沒有可重現的代碼示例。所以不可能提供具體的建議。但我的兩條建議是:

  • 傳遞一個ParallelOptions實例,您將MaxDegreeOfParallelism屬性設置爲合理低的值。就像系統中的核心數量,甚至是那個數字減去一個。

  • 請確保您對磁盤沒有太多期望。您應該從磁盤和控制器的已知速度開始,並將其與您獲得的數據吞吐量進行比較。如果它看起來已經處於或接近最大理論吞吐量,則調整並行度的程度甚至更低。

性能優化是基於已知的硬件限制設置切合實際的目標,衡量您的實際性能,然後研究如何改進算法中最昂貴的元素。如果你還沒有完成前兩個步驟,你真的應該從那裏開始。 :)

+0

*類似於系統中的內核數量,或者甚至是該數量減去一個。* CPU不會成爲瓶頸。根據此設置MaxDOP是個不錯的主意。 – 2014-11-08 08:12:39

+0

OP在抱怨他的用戶界面被阻止。所以有些東西在消耗可用的CPU資源。假設他正確地實現了並行代碼(並且我沒有一個好的代碼示例,但沒有保證),這表明雖然I/O應該是瓶頸,但他仍然設法保持CPU的繁忙。解決方法是專門減少併發線程的數量。 – 2014-11-08 08:42:28

0

我得到它的工作;問題是我試圖在AddRange中使用ExtendedObservableCollection,而不是在每個UI調度中多次調用Add ...出於某種原因,這裏列出的方法的性能實際上是比較慢的:ObservableCollection Doesn't support AddRange method, so I get notified for each item added, besides what about INotifyCollectionChanging?

I認爲是因爲它迫使你使用.Reset(重載)而不是.Add(差異)來調用更改通知,所以存在導致瓶頸的某種邏輯。

我很抱歉沒有發佈剩餘的代碼;我真的被這個拋棄了,我會在一瞬間解釋爲什麼。另外,對於遇到同一問題的其他人來說,這可能會有所幫助。在這種情況下,配置工具的主要問題是它們在這裏沒有多大幫助。無論如何,大部分應用程序的時間將花費在閱讀文件上。所以你必須單獨測試所有的調度員。

+0

在這種情況下,異步IO爲您提供了零性能優勢(爲什麼會這樣?)。如果你的代碼變得更快,你要麼改變了別的東西,要麼你的測量錯誤。 – usr 2014-11-08 13:40:09

+0

@usr你說得對,實際上。無論我是否使用ReadAsync,性能都無關緊要。剛剛嘗試過。我想出於某種原因,ReadAsync使用某種「智能調度」來處理這種IO綁定的東西,但我想不是。將編輯答案,謝謝!這是我一直在派遣的方式。 – Alexandru 2014-11-08 17:34:08

0

我不同意你斷言你不能使用WithDegreeOfParallelism,因爲它會在任何PC上使用。您可以根據CPU數量來確定它。通過不使用WithDegreeOfParallelism,你將會在某些PC上被壓制。

您針對頭部不必移動的固態光盤進行了優化。我不認爲這種不受限制的並行設計會在普通光盤(任何PC)上出現。

我想嘗試BlockingCollection有3個隊列:FileStream,StreamReader和ObservableCollection。將FileStream限制爲4--它只需保持在StreamReader之前。沒有平行性。

單頭是單頭。它無法從5或5000個文件中讀取比從1讀取更快的文件。在固態下,不會從文件轉換爲文件 - 在常規光盤上會有明顯的損失。如果你的文件被分割,那麼會有一個重要的懲罰(在普通光盤上)。

您不顯示數據寫入的內容,但下一步是將寫入置於另一個BlockingCollection中BlockingCollection的隊列中。 E.G. sb.Append(文本);在一個單獨的隊列中。 但是這可能比它的價值更高。 保持這一目標接近100%忙於一個連續的文件是最好的你要做的。

private async Task<string> ReadTextAsync(string filePath) 
{ 
    using (FileStream sourceStream = new FileStream(filePath, 
     FileMode.Open, FileAccess.Read, FileShare.Read, 
     bufferSize: 4096, useAsync: true)) 
    { 
     StringBuilder sb = new StringBuilder(); 

     byte[] buffer = new byte[0x1000]; 
     int numRead; 
     while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0) 
     { 
      string text = Encoding.Unicode.GetString(buffer, 0, numRead); 
      sb.Append(text); 
     } 

     return sb.ToString(); 
    } 
} 
+0

非常好的一點。 ReadAsync性能仍然需要在我的應用程序中的非SSD驅動器上進行測試。對於其他人而言:他基本上是說,在RPM驅動器上,使用ReadAsync對上下文切換預定線程的懲罰是有害的,因爲這些驅動器需要機械磁頭移動來讀取扇區。關於這一點,請注意,由於AsParallel內的工作人員的功能,應該*取決於您在該PC上擁有的核心數量,所以它可能不會像我們想象的那樣糟糕,因爲並行度是有限。 – Alexandru 2014-11-08 17:19:40

+0

@Alexandru它不是關於ReadAsync。這是平行的時期。 – Paparazzi 2014-11-08 18:54:54

+0

是的,我從「usr」對我的解決方案的評論中認識到了這一點。無論您是Read還是ReadAsync,如果您通過AsParallel迭代器進行操作,它也是同樣的事情。 – Alexandru 2014-11-08 18:57:59

相關問題