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.