Qual è il modo più veloce per creare un checksum per file di grandi dimensioni in C #


128

Devo sincronizzare file di grandi dimensioni su alcune macchine. I file possono avere dimensioni massime di 6 GB. La sincronizzazione verrà eseguita manualmente ogni poche settimane. Non posso prendere in considerazione il nome del file perché possono cambiare in qualsiasi momento.

Il mio piano è creare checksum sul PC di destinazione e sul PC di origine e quindi copiare tutti i file con un checksum, che non sono già nella destinazione, nella destinazione. Il mio primo tentativo è stato qualcosa del genere:

using System.IO;
using System.Security.Cryptography;

private static string GetChecksum(string file)
{
    using (FileStream stream = File.OpenRead(file))
    {
        SHA256Managed sha = new SHA256Managed();
        byte[] checksum = sha.ComputeHash(stream);
        return BitConverter.ToString(checksum).Replace("-", String.Empty);
    }
}

Il problema era il runtime:
- con SHA256 con un file da 1,6 GB -> 20 minuti
- con MD5 con un file da 1,6 GB -> 6,15 minuti

Esiste un modo migliore - più veloce - per ottenere il checksum (forse con una migliore funzione hash)?


2
Hai davvero bisogno di controllare il Checksum? Come stai copiando i file? Se su Windows userei l'ultima versione di Robocopy ...
Mesh,

6
Un bel consiglio qui per disturbare l'hashing solo se le dimensioni dei file sono diverse tra 2 file candidati stackoverflow.com/a/288756/74585
Matthew Lock

Risposte:


117

Il problema qui è che SHA256Managedlegge 4096 byte alla volta (eredita FileStreame ignora Read(byte[], int, int)per vedere quanto legge dal filestream), che è un buffer troppo piccolo per l'IO del disco.

Per le cose di velocità su (2 minuti per hashing file di 2 GB sulla mia macchina con SHA256, 1 minuto per MD5) involucro FileStreamin BufferedStreame impostare la dimensione del buffer di dimensioni ragionevoli (ho provato con tampone ~ 1 Mb):

// Not sure if BufferedStream should be wrapped in using block
using(var stream = new BufferedStream(File.OpenRead(filePath), 1200000))
{
    // The rest remains the same
}

3
OK - questo ha fatto la differenza - l'hashing del file da 1,6 GB con MD5 ha richiesto 5,2 secondi sulla mia scatola (QuadCode @ 2,6 GHz, 8 GB di RAM) - ancora più veloce come l'implementazione nativa ...
crono

4
non capisco. ho appena provato questo suggerimento, ma la differenza è minima o nulla. File da 1024 MB senza buffering 12-14 secondi, con buffering anche 12-14 secondi - capisco che la lettura di centinaia di blocchi 4k produrrà più IO ma mi chiedo se il framework o le API native sotto il framework non lo gestiscono già ..
Christian Casutt,

11
Un po 'tardi per la festa, ma per FileStreams non è più necessario avvolgere lo stream in un BufferedStream come è già fatto al giorno d'oggi nel FileStream stesso. Fonte
Reyhn,

Stavo attraversando questo problema con file più piccoli (<10 MB, ma impiegando un'eternità per ottenere un MD5). Anche se utilizzo .Net 4.5, il passaggio a questo metodo con BufferedStream riduce il tempo di hash da circa 8,6 secondi a <300 ms per un file da
8,6 MB

Ho usato un BufferedStream / w 512 kB invece di 1024 kB. Il file da 1,8 GB è stato risolto in 30 secondi.
Hugo Woesthuis,

61

Non fare il checksum dell'intero file, crea checksum ogni 100mb circa, quindi ogni file ha una raccolta di checksum.

Quindi, quando si confrontano i checksum, è possibile interrompere il confronto dopo il primo checksum diverso, uscire presto e salvarsi dall'elaborazione dell'intero file.

Ci vorrà ancora tutto il tempo per file identici.


2
Mi piace l'idea, ma non funzionerà nel mio scenario perché finirò con un sacco di file invariati nel tempo.
crono,

1
come si fa a fare il checksum ogni 100 MB di un file?
Smith,

1
Non è una buona idea quando si utilizza il checksum per motivi di sicurezza, perché l'aggressore può semplicemente modificare i byte che hai escluso.
b.kiener

2
+1 Questa è un'ottima idea quando si esegue un confronto uno a uno. Sfortunatamente, sto usando l'hash MD5 come indice per cercare file univoci tra molti duplicati (controlli molti-a-molti).
Nathan Goings,

1
@ b.kiener Nessun byte è escluso. Lo hai frainteso.
Soroush Falahati,

47

Come notato da Anton Gogolev , FileStream legge 4096 byte alla volta per impostazione predefinita, ma puoi specificare qualsiasi altro valore usando il costruttore FileStream:

new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 16 * 1024 * 1024)

Si noti che Brad Abrams di Microsoft ha scritto nel 2004:

non vi è alcun vantaggio dall'avvolgere un BufferedStream attorno a un FileStream. Abbiamo copiato la logica di buffering di BufferedStream in FileStream circa 4 anni fa per incoraggiare migliori prestazioni predefinite

fonte


22

Richiamare la porta di Windows di md5sum.exe . È circa due volte più veloce dell'implementazione di .NET (almeno sulla mia macchina utilizzando un file da 1,2 GB)

public static string Md5SumByProcess(string file) {
    var p = new Process ();
    p.StartInfo.FileName = "md5sum.exe";
    p.StartInfo.Arguments = file;            
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.Start();
    p.WaitForExit();           
    string output = p.StandardOutput.ReadToEnd();
    return output.Split(' ')[0].Substring(1).ToUpper ();
}

3
WOW - l'utilizzo di md5sums.exe da pc-tools.net/win32/md5sums lo rende davvero veloce. 1681457152 byte, 8672 ms = 184,91 MB / sec -> 1,6 GB ~ 9 secondi Questo sarà abbastanza veloce per il mio scopo.
crono,

16

Ok, grazie a tutti voi, lasciami concludere:

  1. usare un exe "nativo" per eseguire l'hash ha richiesto tempo da 6 minuti a 10 secondi, il che è enorme.
  2. L'aumento del buffer è stato ancora più veloce - il file 1.6GB ha impiegato 5,2 secondi usando MD5 in .Net, quindi andrò con questa soluzione - grazie ancora

10

Ho fatto dei test con le dimensioni del buffer, eseguendo questo codice

using (var stream = new BufferedStream(File.OpenRead(file), bufferSize))
{
    SHA256Managed sha = new SHA256Managed();
    byte[] checksum = sha.ComputeHash(stream);
    return BitConverter.ToString(checksum).Replace("-", String.Empty).ToLower();
}

E ho provato con un file di 29½ GB di dimensione, i risultati sono stati

  • 10.000: 369,24s
  • 100.000: 362,55s
  • 1.000.000: 361,53s
  • 10.000.000: 434,15s
  • 100.000.000: 435,15s
  • 1.000.000.000: 434,31s
  • E 376,22 se si utilizza il codice buffer originale, nessuno.

Sono in esecuzione una CPU i5 2500K, 12 GB di RAM e un'unità SSD OCZ Vertex 4 256 GB.

Quindi ho pensato, che dire di un hard disk standard da 2 TB. E i risultati sono stati così

  • 10.000: 368,52s
  • 100.000: 364,15s
  • 1.000.000: 363,06s
  • 10.000.000: 678,96s
  • 100.000.000: 617,89s
  • 1.000.000.000: 626,86s
  • E per nessuno tamponato 368,24

Quindi consiglierei né buffer né buffer di massimo 1 mill.


Non capisco. In che modo questo test può contraddire la risposta accettata da Anton Gogolev?
buddybubble,

Puoi aggiungere una descrizione di ciascun campo nei tuoi dati?
videoguy,

2

Stai facendo qualcosa di sbagliato (probabilmente buffer di lettura troppo piccolo). Su una macchina di età indecente (Athlon 2x1800MP del 2002) che ha DMA su disco probabilmente fuori combattimento (6,6 M / s è dannatamente lento quando si eseguono letture sequenziali):

Crea un file 1G con dati "casuali":

# dd if=/dev/sdb of=temp.dat bs=1M count=1024    
1073741824 bytes (1.1 GB) copied, 161.698 s, 6.6 MB/s

# time sha1sum -b temp.dat
abb88a0081f5db999d0701de2117d2cb21d192a2 *temp.dat

1m5.299s

# time md5sum -b temp.dat
9995e1c1a704f9c1eb6ca11e7ecb7276 *temp.dat

1m58.832s

Anche questo è strano, md5 è costantemente più lento di sha1 per me (ripetere più volte).


Sì, proverò ad aumentare il buffer, come ha suggerito Anton Gogolev. L'ho eseguito attraverso un MD5.exe "nativo" che ha impiegato 9 secondi con un file da 1,6 GB.
crono,

2

So che sono in ritardo per festeggiare ma ho eseguito i test prima di implementare effettivamente la soluzione.

Ho eseguito test contro la classe MD5 integrata e anche md5sum.exe . Nel mio caso la classe integrata ha richiesto 13 secondi, dove anche md5sum.exe ha richiesto circa 16-18 secondi in ogni esecuzione.

    DateTime current = DateTime.Now;
    string file = @"C:\text.iso";//It's 2.5 Gb file
    string output;
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(file))
        {
            byte[] checksum = md5.ComputeHash(stream);
            output = BitConverter.ToString(checksum).Replace("-", String.Empty).ToLower();
            Console.WriteLine("Total seconds : " + (DateTime.Now - current).TotalSeconds.ToString() + " " + output);
        }
    }

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.