2012-07-06 10 views
9

Sto creando un semplice strumento di backup di sincronizzazione cartelle per me stesso e ho eseguito un bel rallentamento utilizzando File.Copy. Facendo dei test copiando una cartella di ~ 44.000 file di piccole dimensioni (cartelle di posta di Windows) su un'altra unità del mio sistema, ho scoperto che usare File.Copy era più lento di 3 volte rispetto all'uso di una riga di comando e di eseguire xcopy per copiare gli stessi file/cartelle. La versione di My C# impiega più di 16 minuti per copiare i file, mentre xcopy richiede solo 5 minuti. Ho provato a cercare aiuto su questo argomento, ma tutto quello che trovo sono le persone che si lamentano della lenta copia di file di grandi dimensioni su una rete. Questo non è né un grosso problema di file né un problema di copia di rete..net File.Copy molto lento durante la copia di molti file di piccole dimensioni (non in rete)

Ho trovato un interesting article about a better File.Copy replacement, ma il codice pubblicato ha alcuni errori che causano problemi con lo stack e non sono affatto abbastanza esperto per risolvere i problemi nel suo codice.

Esistono metodi semplici o semplici per sostituire File.Copy con qualcosa di più veloce?

risposta

4

Una delle cose che rallenta maggiormente le operazioni di I/O sui dischi di rotazione è lo spostamento della testina del disco.

È ragionevole supporre e probabilmente abbastanza preciso che i numerosi file piccoli (che sono tutti correlati tra loro) siano più vicini sul disco di quanto non siano vicini alla destinazione della copia (presupponendo che si copi da uno parte di un disco in un'altra parte dello stesso disco). Se si copia per un bit quindi si scrive per un po ', si apre una finestra di opportunità per altri processi per spostare la testina del disco sul disco di origine o di destinazione.

Una cosa che XCopy fa molto meglio di Copia (ovvero in entrambi i casi i comandi) è che XCopy legge un gruppo di file prima di iniziare a scrivere quei file nella destinazione.

Se si copiano file sullo stesso disco, provare ad allocare un buffer di grandi dimensioni per leggere più file contemporaneamente, quindi scrivere quei file una volta che il buffer è pieno).

Se si sta leggendo da un disco e si scrive su un altro disco, provare ad avviare un thread per leggere dal disco di origine e un thread separato per scrivere sull'altro disco.

+0

Grazie per le buone informazioni! Ero particolarmente interessato a provare a buffering delle letture/scritture come hai descritto XCopy. Ho fatto alcuni test con un buffer da 50mb e ho scoperto che il mio tempo di copia era ridotto a 14 min 40 sec. Quindi non un miglioramento sorprendente, ma migliore. Ancora anni luce dietro i tempi di XCopy. Vedrò se il threading delle letture/scritture aiuta il prossimo ... – Guavaman

+0

In realtà, sono tornato a 16 minuti dopo che ho realizzato che il mio sistema di copia buffered basato su FileStream non stava copiando le proprietà del file (attributi, tempo di creazione, ecc.) Dopo averli aggiunti di nuovo, il tempo di copia è tornato al punto in cui si trovava con File.Copy, solo che ho perso 50 MB di memoria per il buffering. :( – Guavaman

0

Non ho una buona esperienza a questo livello. Perché non provi a eseguire un file batch contenente i comandi xcopy? Controlla questo post: Executing Batch File in C#

8

Una cosa da considerare è se la tua copia ha un'interfaccia utente che si aggiorna durante la copia. In tal caso, assicurati che la tua copia sia in esecuzione su un thread separato o che l'interfaccia utente si blocchi durante la copia e che la copia venga rallentata effettuando chiamate di blocco per aggiornare l'interfaccia utente.

Ho scritto un programma simile e nella mia esperienza, il mio codice è più veloce di una copia di Windows Explorer (non sono sicuro di xcopy dal prompt dei comandi).

Anche se si dispone di un'interfaccia utente, non aggiornare su ogni file; aggiorna invece ogni X megabyte o ogni Y file (a seconda dell'evento che si verifica per primo), questo riduce la quantità di aggiornamenti a qualcosa che l'IU può effettivamente gestire. Ho usato tutti i file .5 MB o 10; quelli potrebbero non essere ottimali, ma ha aumentato notevolmente la velocità di copia e la reattività dell'interfaccia utente.

Un altro modo per accelerare le cose consiste nell'utilizzare le funzioni Enumerazione anziché Get (ad esempio EnumerateFiles anziché GetFiles). Queste funzioni iniziano a restituire i risultati il ​​prima possibile invece di aspettare di restituire tutto quando la lista è finita di essere costruita. Restituiscono un Enumerable, quindi puoi semplicemente chiamare foreach sul risultato: foreach (file di stringa in System.IO.Directory.EnumerateDirectories(path)).Anche per il mio programma questo ha fatto una notevole differenza di velocità, e sarebbe stato ancora più utile in casi come il tuo in cui hai a che fare con directory contenenti molti file.

+3

+1 .Soprattutto per l'uso dell'enumerazione pigra –

+0

Ho la mia interfaccia su un thread in background Grazie per i suggerimenti su come aggiornare lo schermo meno spesso Sfortunatamente, questo non è davvero un problema per i miei tempi di copia. Aggiornamento UI completamente disabilitato, il tempo di copia è lo stesso di prima.Vedrò se l'utilizzo di EnumerateFiles aiuta. – Guavaman

+0

Ho appena ottenuto i risultati dell'utilizzo degli oggetti IEnumerables.Non ha aiutato affatto i tempi di copia dei file, probabilmente perché sto andando cartella per cartella copia i file quindi non ci vuole molto tempo per fare i GetFile la maggior parte del tempo, ma aiuta un po 'a rendere più semplice il processo di conteggio dei file iniziale – Guavaman

0

Penso che potresti almeno metterlo in parallelo in modo che tu faccia due file allo stesso tempo. Mentre un thread sta scrivendo un altro può già leggere il file successivo. Se hai un elenco dei file puoi farlo in questo modo. Utilizzando molti fili non aiuterà, perché questo renderà la mossa unità intorno molto di più invece di essere in grado di scrivere in modo sequenziale ..

var files = new List<string>(); 
// todo: fill the files list using directoryenumeration or so... 
var po = new ParallelOptions() {MaxDegreeOfParallelism = 2}; 
Parallel.ForEach(files, po, CopyAFile); 

// Routine to copy a single file 
private void CopyAFile(string file) { } 
1

Ci sono due algoritmi per la copia di file più veloci:

Se sorgente e la destinazione sono diversi dischi Quindi:

  • Un thread legge continuamente i file e li memorizza in un buffer.
  • Un altro thread che scrive file continuamente da quel buffer.

Se sorgente e la destinazione è lo stesso disco, allora:

  • Leggi un pezzo fisso di byte, dicono 8K alla volta, indipendentemente dal numero di file che è.
  • Scrivi quel chunk fisso a destinazione, in un unico file o in più file.

In questo modo otterrete prestazioni significative.

In alternativa è sufficiente richiamare xcopy dal codice .net. Perché preoccuparsi di farlo usando File.Copy. È possibile acquisire l'output di xcopy utilizzando Process.StandardOutput e mostrarlo sullo schermo per mostrare all'utente cosa sta succedendo.

Problemi correlati