Quel est le moyen le plus rapide de créer une somme de contrôle pour les fichiers volumineux en C #

129

Je dois synchroniser de gros fichiers sur certaines machines. Les fichiers peuvent atteindre jusqu'à 6 Go. La synchronisation sera effectuée manuellement toutes les quelques semaines. Je ne peux pas prendre le nom de fichier en considération car ils peuvent changer à tout moment.

Mon plan est de créer des sommes de contrôle sur le PC de destination et sur le PC source, puis de copier tous les fichiers avec une somme de contrôle, qui ne sont pas déjà dans la destination, vers la destination. Ma première tentative était quelque chose comme ceci:

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

Le problème était l'exécution:
- avec SHA256 avec un fichier de 1,6 Go -> 20 minutes
- avec MD5 avec un fichier de 1,6 Go -> 6,15 minutes

Existe-t-il un meilleur moyen - plus rapide - d'obtenir la somme de contrôle (peut-être avec une meilleure fonction de hachage)?

crono
la source
2
Avez-vous vraiment besoin de vérifier la somme de contrôle? Comment copiez-vous les fichiers? Si vous êtes sous Windows, j'utiliserais la dernière version de Robocopy ...
Mesh
6
Bonne astuce ici pour ne déranger le hachage que si la taille des fichiers est différente entre 2 fichiers candidats stackoverflow.com/a/288756/74585
Matthew Lock

Réponses:

117

Le problème ici est que SHA256Managedlit 4096 octets à la fois (hériter de FileStreamet remplacerRead(byte[], int, int) pour voir combien il lit dans le flux de fichiers), ce qui est un tampon trop petit pour les E / S de disque.

Pour accélérer les choses (2 minutes pour hachant fichier 2 Gb sur ma machine avec SHA256, 1 minute pour MD5) wrap FileStreamdans BufferedStreamet définir la taille de la mémoire tampon de taille raisonnable (j'ai essayé avec un tampon ~ 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
}
Anton Gogolev
la source
3
OK - cela a fait la différence - le hachage du fichier de 1,6 Go avec MD5 a pris 5,2 secondes sur ma boîte (QuadCode @ 2,6 GHz, 8 Go de RAM) - encore plus rapide que l'implémentation native ...
crono
4
je ne comprends pas. J'ai juste essayé cette suggestion mais la différence est minime ou nulle. Fichier de 1024 Mo sans mise en mémoire tampon de 12 à 14 secondes, avec mise en mémoire tampon également de 12 à 14 secondes - je comprends que la lecture de centaines de blocs de 4k produira plus d'E / S mais je me demande si le framework ou les API natives sous le framework ne le gèrent pas déjà ..
Christian Casutt
11
Un peu tard à la fête, mais pour FileStreams, il n'est plus nécessaire d'envelopper le flux dans un BufferedStream comme c'est déjà fait dans le FileStream lui-même. Source
Reyhn
J'étais juste en train de traverser ce problème avec des fichiers plus petits (<10 Mo, mais il fallait une éternité pour obtenir un MD5). Même si j'utilise .Net 4.5, le passage à cette méthode avec BufferedStream a réduit le temps de hachage d'environ 8,6 secondes à <300 ms pour un fichier de
8,6 Mo
J'ai utilisé un BufferedStream / w 512 ko au lieu de 1024 ko. Le fichier de 1,8 Go a été résolu en 30 secondes.
Hugo Woesthuis
61

Ne faites pas la somme de contrôle du fichier entier, créez des sommes de contrôle tous les 100 Mo environ, de sorte que chaque fichier possède une collection de sommes de contrôle.

Ensuite, lorsque vous comparez les sommes de contrôle, vous pouvez arrêter de comparer après la première somme de contrôle différente, sortir tôt et vous éviter de traiter l'ensemble du fichier.

Cela prendra encore tout le temps pour des fichiers identiques.

Binaire Worrier
la source
2
J'aime l'idée, mais cela ne fonctionnera pas dans mon scénario car je me retrouverai avec beaucoup de fichiers inchangés au fil du temps.
crono
1
comment faites-vous la somme de contrôle tous les 100 Mo d'un fichier?
Smith
1
Ce n'est pas une bonne idée lorsque vous utilisez la somme de contrôle pour des raisons de sécurité, car l'attaquant peut simplement modifier les octets que vous avez exclus.
b.kiener
2
+1 C'est une excellente idée lorsque vous effectuez une comparaison individuelle. Malheureusement, j'utilise le hachage MD5 comme index pour rechercher des fichiers uniques parmi de nombreux doublons (vérifications plusieurs-à-plusieurs).
Nathan Goings
1
@ b.kiener Aucun octet n'est exclu. Vous l'avez mal compris.
Soroush Falahati
47

Comme l'a noté Anton Gogolev , FileStream lit 4096 octets à la fois par défaut, mais vous pouvez spécifier toute autre valeur à l'aide du constructeur FileStream:

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

Notez que Brad Abrams de Microsoft a écrit en 2004:

il n'y a aucun avantage à envelopper un BufferedStream autour d'un FileStream. Nous avons copié la logique de mise en mémoire tampon de BufferedStream dans FileStream il y a environ 4 ans pour encourager de meilleures performances par défaut

la source

Tal Aloni
la source
22

Appelez le port Windows de md5sum.exe . C'est environ deux fois plus rapide que l'implémentation .NET (au moins sur ma machine en utilisant un fichier de 1,2 Go)

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 ();
}
Christian Birkl
la source
3
WOW - utiliser md5sums.exe à partir de pc-tools.net/win32/md5sums le rend vraiment rapide. 1681457152 octets, 8672 ms = 184,91 Mo / sec -> 1,6 Go ~ 9 secondes Ce sera assez rapide pour mon objectif.
crono
10

J'ai fait des tests avec la taille du tampon, en exécutant ce code

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

Et j'ai testé avec un fichier de 29½ Go, les résultats étaient

  • 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
  • Et 376,22s lors de l'utilisation du code original, sans tampon.

J'utilise un processeur i5 2500K, 12 Go de RAM et un disque SSD OCZ Vertex 4 256 Go.

Alors j'ai pensé, qu'en est-il d'un disque dur standard de 2 To. Et les résultats étaient comme ça

  • 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
  • Et pour aucun tamponné 368,24

Je recommanderais donc soit pas de tampon, soit un tampon de max 1 mill.

Anders
la source
Je ne comprends pas. Comment ce test peut-il contredire la réponse acceptée d'Anton Gogolev?
buddybubble
Pouvez-vous ajouter une description de chaque champ dans vos données?
videoguy
2

Vous faites quelque chose de mal (probablement trop petit tampon de lecture). Sur une machine d'un âge indécent (Athlon 2x1800MP de 2002) qui a DMA sur le disque probablement hors de contrôle (6,6M / s est sacrément lent lors de lectures séquentielles):

Créez un fichier 1G avec des données "aléatoires":

# 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

C'est aussi bizarre, md5 est toujours plus lent que sha1 pour moi (relance plusieurs fois).

Pasi Savolainen
la source
Oui - je vais essayer d'augmenter le tampon - comme Anton Gogolev l'a suggéré. Je l'ai parcouru via un MD5.exe "natif" qui a pris 9 secondes avec un fichier de 1,6 Go.
crono
2

Je sais que je suis en retard pour faire la fête mais que j'ai effectué des tests avant de mettre en œuvre la solution.

J'ai effectué des tests contre la classe MD5 intégrée et aussi md5sum.exe . Dans mon cas, la classe intégrée a pris 13 secondes où md5sum.exe environ 16-18 secondes à chaque exécution.

    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);
        }
    }
Romil Kumar Jain
la source