2009-08-07 12 views
32

Il mio problema riguarda le prestazioni di copia dei file. Abbiamo un sistema di gestione dei media che richiede molti file in movimento sul file system in diverse posizioni, comprese le condivisioni Windows sulla stessa rete, siti FTP, AmazonS3, ecc. Quando eravamo tutti su una rete Windows, potevamo farcela usando System.IO.File.Copy (origine, destinazione) per copiare un file. Dal momento che molte volte abbiamo un input Stream (come un MemoryStream), abbiamo provato ad astrarre l'operazione Copy per prendere un input Stream e un flusso di output, ma stiamo assistendo a una massiccia diminuzione delle prestazioni. Di seguito è riportato un codice per copiare un file da utilizzare come punto di discussione.File.Copy vs. Manual FileStream.Write per copiare file

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(); 
     } 
    } 
} 

Qualcuno sa perché questo esegue in modo molto più lento di File.Copy? C'è qualcosa che posso fare per migliorare le prestazioni? Devo solo inserire una logica speciale per vedere se sto copiando da una posizione Windows a un'altra - nel qual caso userò semplicemente File.Copy e negli altri casi userò i flussi?

Per favore fatemi sapere cosa ne pensate e se avete bisogno di ulteriori informazioni. Ho provato diverse dimensioni del buffer e sembra che una dimensione del buffer di 64k sia ottimale per i nostri file "piccoli" e 256k + è una dimensione del buffer migliore per i nostri file "grandi" - ma in entrambi i casi ha prestazioni molto peggiori di File.Copy (). Grazie in anticipo!

+3

Questo potrebbe avere qualcosa a che fare con l'interoperabilità nativa. Il mio sospetto è che File.Copy() e le operazioni di flusso di I/O siano costruite sopra l'API di Windows e che il flusso di chiamata lettura/scrittura ripetutamente in un ciclo sia più costoso rispetto a una chiamata nativa di file di copia che File.Copy() farà. –

+0

@Steve: hai ragione, vedi la mia risposta. –

risposta

23

File.Copy stato costruito intorno al CopyFile funzione Win32 e questa funzione richiede molta attenzione da parte dell'equipaggio MS (ricordate questo Vista -related threads about slow copy performance).

Diversi indizi per migliorare le prestazioni del metodo:

  1. Come molti hanno detto in precedenza rimuovere metodo Flush dal ciclo. Non ne hai affatto bisogno.
  2. L'aumento del buffer può essere d'aiuto, ma solo per le operazioni da file a file, per le condivisioni di rete o server ftp questo rallenterà invece. 60 * 1024 è l'ideale per le condivisioni di rete, almeno prima di vista. per ftp 32k sarà sufficiente nella maggior parte dei casi.
  3. Aiuta gli utenti fornendo la tua strategia di caching (nel tuo caso lettura e scrittura sequenziali), usa la funzione di costruzione del costruttore FileStream con il parametro FileOptions (SequentalScan).
  4. È possibile velocizzare la copia utilizzando il modello asincrono (particolarmente utile per i casi da rete a file), ma non utilizzare thread per questo, utilizzare invece io sovrapposto (BeginRead, EndRead, BeginWrite, EndWrite in .net) e non dimenticate set di opzioni asincrono nel costruttore FileStream (vedi FileOptions)

Esempio di modello copia asincrona:

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

Una cosa che spicca è che stai leggendo un pezzo, scrivendo quel pezzo, leggendo un altro pezzo e così via.

Le operazioni di streaming sono ottimi candidati per il multithreading. La mia ipotesi è che File.Copy implementa il multithreading.

Provare a leggere in una discussione e scrivere in un'altra discussione. Sarà necessario coordinare i thread in modo che il thread di scrittura non inizi a cancellare un buffer finché non viene completato il thread di lettura. È possibile risolvere questo problema con due buffer, uno che viene letto mentre l'altro viene scritto e un flag che indica quale buffer è attualmente utilizzato per quale scopo.

+0

Attualmente sto studiando il multithreading. Ci sono buoni progetti open source che fanno esattamente questo effetto? Continuerò a indagare. Grazie per la risposta rapida. – jakejgordon

1

Provare a rimuovere la chiamata Flush e spostarla fuori dal loop.

A volte il sistema operativo conosce meglio quando scaricare l'IO .. Consente di utilizzare meglio i suoi buffer interni.

+0

Inoltre, non penso che l'operazione Copia implichi il multithreading e personalmente considererei una cattiva idea. Significa creare un thread per ogni operazione di copia, che è presumibilmente ancora più costosa di un semplice utilizzo degli stream. –

+0

@aviadbenov: è vero che creare i nostri thread per gestire le operazioni IO è eccessivo. Tuttavia .NET mantiene un pool di thread espressamente per questo scopo. L'uso corretto delle chiamate IO asincrone ci consente di utalizzare questi thread senza doverli creare e distruggere da soli. – AnthonyWJones

+0

@Anthony: quello che dici è vero ma anche pericoloso. Se molti thread dovessero copiare i file, il pool di thread stesso diventerebbe il collo della bottiglia dell'operazione di copiatura! –

4

Tre cambiamenti miglioreranno notevolmente le prestazioni:

  1. Aumentare la dimensione del buffer, provare a 1MB (esperimento ben -proprio)
  2. Dopo aver aperto il vostro FileStream, chiamare fileStream.SetLength (inStream.Length) per allocare l'intero blocco su disco in primo piano (funziona solo se inStream è ricercabile)
  3. Rimuovi fileStream.Flush() - è ridondante e probabilmente ha il maggiore impatto sulle prestazioni poiché bloccherà fino al completamento dello svuotamento. Lo stream verrà comunque svuotato per lo smaltimento.

questo sembrava circa 3-4 volte più veloce negli esperimenti che ho provato:

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

Mark Russinovich sarebbe l'autorità su questo.

Ha scritto sulla sua blog una voce Inside Vista SP1 File Copy Improvements che riassume lo stato dell'arte di Windows attraverso Vista SP1.

La mia ipotesi semi-istruita sarebbe che File.Copy sarebbe più robusto sul maggior numero di situazioni. Naturalmente, questo non significa che in qualche caso specifico angolo, il proprio codice potrebbe batterlo ...

7

Rispolverando riflettore possiamo vedere che File.Copy in realtà chiama l'API Win32:

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

che risolve a

[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

non sarai mai andare a in grado di battere il sistema operativo a fare qualcosa di così fundemental con il proprio codice, nemmeno se si fatto con cura in assembler.

Se è necessario assicurarsi che le operazioni si verifichino con le migliori prestazioni E si desidera combinare e abbinare varie fonti, sarà necessario creare un tipo che descriva le ubicazioni delle risorse. Quindi si crea un'API che ha funzioni come Copy che accetta due tipi di questo tipo e dopo aver esaminato le descrizioni di entrambi sceglie il meccanismo di copia con la migliore prestazione. Ad esempio, dopo aver stabilito che entrambe le posizioni sono posizioni dei file Windows, scegliere File.Copy OPPURE se l'origine è il file Windows, ma la destinazione deve essere HTTP POST utilizza una WebRequest.

Problemi correlati