Was ist der schnellste Weg, um eine Prüfsumme für große Dateien in C # zu erstellen?


128

Ich muss große Dateien auf einigen Computern synchronisieren. Die Dateien können bis zu 6 GB groß sein. Die Synchronisierung erfolgt alle paar Wochen manuell. Ich kann den Dateinamen nicht berücksichtigen, da sie sich jederzeit ändern können.

Mein Plan ist es, Prüfsummen auf dem Ziel-PC und auf dem Quell-PC zu erstellen und dann alle Dateien mit einer Prüfsumme, die sich noch nicht im Ziel befinden, auf das Ziel zu kopieren. Mein erster Versuch war ungefähr so:

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

Das Problem war die Laufzeit:
- mit SHA256 mit einer 1,6-GB-Datei -> 20 Minuten
- mit MD5 mit einer 1,6-GB-Datei -> 6,15 Minuten

Gibt es einen besseren - schnelleren - Weg, um die Prüfsumme zu erhalten (möglicherweise mit einer besseren Hash-Funktion)?


2
Müssen Sie wirklich die Prüfsumme überprüfen? Wie kopierst du die Dateien? Wenn Sie unter Windows sind, würde ich die neueste Version von Robocopy verwenden ...
Mesh

6
Netter Tipp hier, um nur Hashing zu stören, wenn die Dateigrößen zwischen 2 Kandidatendateien unterschiedlich sind stackoverflow.com/a/288756/74585
Matthew Lock

Antworten:


117

Das Problem hierbei ist, dass jeweils SHA256Managed4096 Bytes gelesen werden (erben von FileStreamund überschreiben, um Read(byte[], int, int)zu sehen, wie viel vom Dateistream gelesen wird), was ein zu kleiner Puffer für Festplatten-E / A ist.

Um Dinge zu beschleunigen (2 Minuten für Hashing 2 GB - Datei auf meinem Rechner mit SHA256, 1 Minute für MD5) Wrap FileStreamin BufferedStreamund setzt in üblichen Größe Puffergröße (Ich habe versucht , mit ~ 1 Mb - Puffer):

// 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 - das machte den Unterschied - das Hashing der 1,6-GB-Datei mit MD5 dauerte auf meiner Box 5,2 Sekunden (QuadCode bei 2,6 GHz, 8 GB RAM) - sogar schneller als die native Implementierung ...
crono

4
Ich verstehe es nicht. Ich habe gerade diesen Vorschlag ausprobiert, aber der Unterschied ist minimal bis gar nicht. 1024-MB-Datei ohne Pufferung von 12 bis 14 Sekunden, mit Pufferung auch von 12 bis 14 Sekunden - ich verstehe, dass das Lesen von Hunderten von 4-KB-Blöcken mehr E / A erzeugt, aber ich frage mich, ob das Framework oder die nativen APIs unter dem Framework dies nicht bereits behandeln ..
Christian Casutt

11
Ein bisschen zu spät zur Party, aber für FileStreams besteht keine Notwendigkeit mehr, den Stream in einen BufferedStream zu packen, wie dies heutzutage bereits im FileStream selbst geschieht. Quelle
Reyhn

Ich habe dieses Problem nur mit kleineren Dateien durchgearbeitet (<10 MB, aber es hat ewig gedauert, bis ich ein MD5 bekommen habe). Obwohl ich .Net 4.5 verwende, hat das Umschalten auf diese Methode mit dem BufferedStream die Hash-Zeit für eine 8,6-MB-Datei von ca. 8,6 Sekunden auf <300 ms reduziert
Taegost

Ich habe einen BufferedStream / w 512 kB anstelle von 1024 kB verwendet. Die 1,8-GB-Datei wurde in 30 Sekunden gelöst.
Hugo Woesthuis

61

Prüfen Sie nicht die gesamte Datei, sondern erstellen Sie etwa alle 100 MB Prüfsummen, damit jede Datei eine Sammlung von Prüfsummen enthält.

Wenn Sie dann Prüfsummen vergleichen, können Sie den Vergleich nach der ersten anderen Prüfsumme beenden, frühzeitig aussteigen und sich die Verarbeitung der gesamten Datei ersparen.

Für identische Dateien dauert es immer noch die volle Zeit.


2
Ich mag die Idee, aber sie wird in meinem Szenario nicht funktionieren, da ich im Laufe der Zeit viele unveränderte Dateien haben werde.
Crono

1
Wie prüft man alle 100 MB einer Datei?
Smith

1
Keine gute Idee, wenn Sie aus Sicherheitsgründen eine Prüfsumme verwenden, da der Angreifer nur die von Ihnen ausgeschlossenen Bytes ändern kann.
b.kiener

2
+1 Dies ist eine hervorragende Idee, wenn Sie einen Eins-zu-Eins-Vergleich durchführen. Leider verwende ich den MD5-Hash als Index, um nach eindeutigen Dateien unter vielen Duplikaten zu suchen (viele-zu-viele-Prüfungen).
Nathan Goings

1
@ b.kiener Kein Byte ist ausgeschlossen. Du hast ihn missverstanden.
Soroush Falahati

47

Wie Anton Gogolev feststellte , liest FileStream standardmäßig jeweils 4096 Byte. Sie können jedoch mit dem FileStream-Konstruktor jeden anderen Wert angeben:

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

Beachten Sie, dass Brad Abrams von Microsoft im Jahr 2004 schrieb:

Es hat keinen Vorteil, einen BufferedStream um einen FileStream zu wickeln. Wir haben die Pufferlogik von BufferedStream vor ungefähr 4 Jahren in FileStream kopiert, um eine bessere Standardleistung zu fördern

Quelle


22

Rufen Sie den Windows-Port von md5sum.exe auf . Es ist ungefähr doppelt so schnell wie die .NET-Implementierung (zumindest auf meinem Computer mit einer 1,2-GB-Datei).

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 - die Verwendung von md5sums.exe von pc-tools.net/win32/md5sums macht es sehr schnell. 1681457152 Bytes, 8672 ms = 184,91 MB / s -> 1,6 GB ~ 9 Sekunden Dies ist für meinen Zweck schnell genug.
Crono

16

Ok - danke an euch alle - lasst mich das zusammenfassen:

  1. Die Verwendung einer "nativen" Exe für das Hashing dauerte zwischen 6 Minuten und 10 Sekunden, was enorm ist.
  2. Das Erhöhen des Puffers war noch schneller - 1,6 GB Dateien dauerten mit MD5 in .Net 5,2 Sekunden, daher werde ich mich für diese Lösung entscheiden - nochmals vielen Dank

10

Ich habe Tests mit Puffergröße durchgeführt und diesen Code ausgeführt

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

Und ich habe mit einer Datei von 29½ GB Größe getestet, die Ergebnisse waren

  • 10.000: 369,24 s
  • 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
  • Und 376,22s bei Verwendung des ursprünglichen, nicht gepufferten Codes.

Ich verwende eine i5 2500K-CPU, 12 GB RAM und ein OCZ Vertex 4 256 GB SSD-Laufwerk.

Also dachte ich mir, was ist mit einer Standard-2-TB-Festplatte? Und die Ergebnisse waren so

  • 10.000: 368,52s
  • 100.000: 364,15 s
  • 1.000.000: 363,06s
  • 10.000.000: 678,96s
  • 100.000.000: 617.89s
  • 1.000.000.000: 626,86s
  • Und für niemanden gepuffert 368,24

Daher würde ich entweder keinen Puffer oder einen Puffer von maximal 1 Mühle empfehlen.


Ich verstehe es nicht. Wie kann dieser Test der akzeptierten Antwort von Anton Gogolev widersprechen?
Buddybubble

Können Sie eine Beschreibung jedes Felds in Ihre Daten einfügen?
Videoguy

2

Sie machen etwas falsch (wahrscheinlich zu kleiner Lesepuffer). Auf einer Maschine im unalterten Alter (Athlon 2x1800MP von 2002), auf der DMA auf der Festplatte wahrscheinlich aus dem Ruder gelaufen ist (6,6 M / s sind verdammt langsam, wenn sequentielle Lesevorgänge ausgeführt werden):

Erstellen Sie eine 1G-Datei mit "zufälligen" Daten:

# 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

Das ist auch komisch, md5 ist für mich durchweg langsamer als sha1 (mehrmals wiederholen).


Ja - ich werde versuchen, den Puffer zu erhöhen - wie Anton Gogolev vorgeschlagen hat. Ich habe es durch eine "native" MD5.exe geführt, die 9 Sekunden mit einer 1,6-GB-Datei dauerte.
Crono

2

Ich weiß, dass ich zu spät zur Party komme, aber einen Test durchgeführt habe, bevor ich die Lösung tatsächlich implementiert habe.

Ich habe einen Test gegen die eingebaute MD5-Klasse und auch gegen md5sum.exe durchgeführt . In meinem Fall dauerte die eingebaute Klasse 13 Sekunden, wobei md5sum.exe bei jedem Lauf ebenfalls etwa 16 bis 18 Sekunden dauerte.

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.