2009-08-07 37 views
32

我的問題是關於文件複製性能。我們有一個媒體管理系統,需要將文件系統上的大量移動文件移動到不同的位置,包括同一網絡上的Windows共享,FTP站點,AmazonS3等等。當我們都在一個Windows網絡中時,我們可以放棄使用System.IO.File.Copy(源,目標)複製文件。由於很多時候我們只有一個輸入流(就像一個MemoryStream),我們嘗試抽象複製操作來獲取輸入流和輸出流,但是我們看到性能大幅下降。以下是一些用於複製文件以用作討論點的代碼。File.Copy與手動FileStream.Write複製文件

public void Copy(System.IO.Stream inStream, string outputFilePath) 
{ 
    int bufferSize = 1024 * 64; 

    using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write)) 
    { 

     int bytesRead = -1; 
     byte[] bytes = new byte[bufferSize]; 

     while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0) 
     { 
      fileStream.Write(bytes, 0, bytesRead); 
      fileStream.Flush(); 
     } 
    } 
} 

有誰知道爲什麼這樣執行比File.Copy慢得多?有什麼我可以做的改善表現?我只需要添加特殊的邏輯來查看是否從一個窗口位置複製到另一個窗口位置 - 在這種情況下,我只是使用File.Copy,而在其他情況下,我將使用這些流?

請讓我知道您的想法以及您是否需要其他信息。我嘗試了不同的緩衝區大小,似乎64k緩衝區大小對於我們的「小」文件是最佳的,256k +對於我們的「大」文件來說是更好的緩衝區大小 - 但是在任何情況下,它的性能都比File.Copy )。提前致謝!

+3

這可能與本機互操作有關。我懷疑File.Copy()和流IO操作是建立在Windows API之上的,並且在一個循環中重複調用流讀/寫比一個本地拷貝文件調用File.Copy()更加昂貴,會做出。 – 2009-08-07 20:50:13

+0

@Steve:你是對的,看到我的迴應。 – 2009-08-07 21:26:18

回答

23

File.Copy是圍繞打造CopyFile Win32函數和此功能需要大量的注意力從MS船員(記住這個Vista的關於慢速複製性能的相關線程)。

幾條線索來提高你的方法的性能:

  1. 像許多剛纔說從循環中除去沖洗方法。你根本不需要它。
  2. 增加緩衝區可能會有所幫助,但只有在文件到文件操作,網絡共享或ftp服務器上纔會有所緩慢。至少在vista之前,60 * 1024是網絡共享的理想選擇。對於大多數情況下ftp 32k就足夠了。
  3. 通過提供緩存策略(在您的情況下順序讀取和寫入)幫助OS,使用FileStream構造函數覆蓋FileOptions參數(SequentalScan)。
  4. 您可以通過使用異步模式加速複製(尤其適用於網絡到文件的情況),但不要使用線程,而是使用重疊的io(BeginRead,EndRead,BeginWrite,EndWrite in .net)和不要忘記的FileStream構造函數中設置異步選項(參見FileOptions)異步複製圖案的

例子:

int Readed = 0; 
IAsyncResult ReadResult; 
IAsyncResult WriteResult; 

ReadResult = sourceStream.BeginRead(ActiveBuffer, 0, ActiveBuffer.Length, null, null); 
do 
{ 
    Readed = sourceStream.EndRead(ReadResult); 

    WriteResult = destStream.BeginWrite(ActiveBuffer, 0, Readed, null, null); 
    WriteBuffer = ActiveBuffer; 

    if (Readed > 0) 
    { 
     ReadResult = sourceStream.BeginRead(BackBuffer, 0, BackBuffer.Length, null, null); 
     BackBuffer = Interlocked.Exchange(ref ActiveBuffer, BackBuffer); 
    } 

    destStream.EndWrite(WriteResult); 
    } 
    while (Readed > 0); 
1

突出的一件事是您正在閱讀一個塊,編寫該塊,讀取另一個塊等。

流媒體操作是多線程的好選擇。我的猜測是File.Copy實現了多線程。

嘗試在一個線程中讀取並在另一個線程中寫入。您需要協調這些線程,以便寫入線程不會開始寫入緩衝區,直到讀取線程完成填充。你可以通過使用兩個緩衝區來解決這個問題,一個在另一個正在寫入時正在讀取,另一個標誌則說明哪個緩衝區當前正在用於哪個目的。

+0

我目前正在調查多線程。是否有很好的開源項目能夠做到這一點?我會繼續調查。感謝您及時的回覆。 – jakejgordon 2009-08-07 20:52:34

1

嘗試刪除Flush調用,並將其移動到循環之外。

有時操作系統知道什麼時候刷新IO ..它允許它更好地使用它的內部緩衝區。

+0

我也不認爲複製操作涉及多線程,我個人認爲這是一個壞主意。這意味着要爲每個複製操作創建一個線程,這可能比使用流成本更高。 – 2009-08-07 20:56:45

+0

@aviadbenov:創建自己的線程來處理IO操作確實是過度的。但是.NET爲此目的明確維護了一個線程池。使用異步IO調用正確地允許我們禁止這些線程而不必自己創建和銷燬它們。 – AnthonyWJones 2009-08-07 22:03:40

+0

@Anthony:你說的是真的,但也很危險。如果許多線程將複製文件,則線程池本身將成爲複製操作的瓶頸! – 2009-08-08 06:35:14

4

三個變化將極大地提高性能:

  1. 你打開你的FILESTREAM後增加您的緩衝區的大小,儘量1MB(井 - 只是實驗)
  2. ,調用fileStream.SetLength(inStream.Length)分配磁盤前面的整個磁盤塊(僅在可搜索inStream時才起作用)
  3. 移除fileStream.Flush() - 它是多餘的,可能會對性能產生最大的影響,因爲它會阻塞,直到刷新完成。無論如何,在處置時流將被刷新。

這似乎在我嘗試了實驗快約3-4倍:

public static void Copy(System.IO.Stream inStream, string outputFilePath) 
    { 
     int bufferSize = 1024 * 1024; 

     using (FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.Write)) 
     { 
      fileStream.SetLength(inStream.Length); 
      int bytesRead = -1; 
      byte[] bytes = new byte[bufferSize]; 

      while ((bytesRead = inStream.Read(bytes, 0, bufferSize)) > 0) 
      { 
       fileStream.Write(bytes, 0, bytesRead); 
      } 
     } 
    } 
1

標記Russinovich將這個權力。

他在他的blog上寫了一個條目Inside Vista SP1 File Copy Improvements,其中總結了Windows SP1的最新技術狀態。

我的半受教育的猜測是File.Copy在最多的情況下是最健壯的。當然,這並不在某些特定的極端情況的意思是,你自己的代碼可能戰勝它......

7

噴粉關閉反射器我們可以看到,實際上File.Copy調用Win32 API:

if (!Win32Native.CopyFile(fullPathInternal, dst, !overwrite)) 

解析爲

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)] 
internal static extern bool CopyFile(string src, string dst, bool failIfExists); 

And here is the documentation for CopyFile

6

你永遠不會能夠在做一些這樣fundemental用自己的代碼擊敗操作系統,甚至沒有你在彙編程序中精心製作。

如果您需要確保您的操作以最佳性能進行並且您想要混合和匹配各種來源,那麼您將需要創建一個描述資源位置的類型。然後創建一個具有如Copy這樣的功能的API,其中包含兩種此類類型,並檢查了兩者的描述以選擇最佳執行的複製機制。例如,在確定兩個位置都是Windows文件位置的情況下,它會選擇File.Copy,或者如果源是Windows文件,但目標是HTTP POST,則使用WebRequest。

相關問題