Qual é a maneira mais rápida de criar uma soma de verificação para arquivos grandes em C #

128

Eu tenho que sincronizar arquivos grandes em algumas máquinas. Os arquivos podem ter até 6 GB de tamanho. A sincronização será feita manualmente a cada poucas semanas. Não posso levar o nome do arquivo em consideração porque eles podem mudar a qualquer momento.

Meu plano é criar somas de verificação no PC de destino e no PC de origem e copiar todos os arquivos com uma soma de verificação, que ainda não está no destino, para o destino. Minha primeira tentativa foi algo assim:

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

O problema era o tempo de execução:
- com SHA256 com um arquivo de 1,6 GB -> 20 minutos
- com MD5 com um arquivo de 1,6 GB -> 6,15 minutos

Existe uma maneira melhor - mais rápida - de obter a soma de verificação (talvez com uma melhor função de hash)?

crono
fonte
2
Você realmente precisa verificar a soma de verificação? Como você está copiando os arquivos? Se o seu no Windows eu usaria a última versão do Robocopy ...
Malha
6
Bela dica aqui para incomodar única hashing se os tamanhos de arquivo são diferentes entre 2 arquivos candidatos stackoverflow.com/a/288756/74585
Matthew Bloqueio

Respostas:

117

O problema aqui é que SHA256Managedlê 4096 bytes de cada vez (herda FileStreame substitui Read(byte[], int, int)para ver o quanto ele lê do fluxo de arquivos), que é um buffer muito pequeno para E / S de disco.

Para acelerar as coisas (2 minutos para hash arquivo de 2 GB na minha máquina com SHA256, 1 minuto para MD5) envoltório FileStreamem BufferedStreame definir o tamanho do buffer de tamanho razoável (eu tentei com tampão de ~ 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
fonte
3
OK - isso fez a diffence - hash o arquivo de 1,6 GB com MD5 levou 5,2 segundos na minha caixa (QuadCode @ 2.6 GHz, 8 GB de RAM) - ainda mais rápido como o implementaion nativa ...
crono
4
Eu não entendo. Eu apenas tentei essa sugestão, mas a diferença é mínima para nada. Arquivo 1024mb sem buffer de 12 a 14 segundos, com buffer também de 12 a 14 segundos - eu entendo que a leitura de centenas de blocos de 4k produzirá mais IO, mas eu me pergunto se a estrutura ou as APIs nativas abaixo da estrutura ainda não lidam com isso. ..
Christian Casutt
11
Um pouco atrasado para a festa, mas para o FileStreams não há mais a necessidade de agrupar o fluxo em um BufferedStream, como já é feito atualmente no próprio FileStream. Fonte
Reyhn
Eu estava passando por esse problema com arquivos menores (<10 MB, mas demorando uma eternidade para obter um MD5). Mesmo que eu use .Net 4.5, a mudança para este método com o BufferedStream reduzir o tempo de hash para baixo de cerca de 8,6 segundos para <300 ms para um arquivo de 8.6MB
Taegost
Eu usei um BufferedStream / w 512 kB em vez de 1024 kB. O arquivo de 1,8 GB foi resolvido em 30 segundos.
Hugo Woesthuis
61

Não faça a soma de verificação do arquivo inteiro, crie somas de verificação a cada 100mb, aproximadamente, para que cada arquivo tenha uma coleção de somas de verificação.

Então, ao comparar somas de verificação, você pode parar de comparar após a primeira soma de verificação diferente, sair cedo e evitar que você processe o arquivo inteiro.

Ainda levará tempo integral para arquivos idênticos.

Preocupação binária
fonte
2
Gosto da ideia, mas ela não funcionará no meu cenário, porque acabarei com muitos arquivos inalterados ao longo do tempo.
crono 24/07/2009
1
como você soma de verificação a cada 100mb de um arquivo?
Smith Smith
1
Não é uma boa idéia ao usar a soma de verificação por motivos de segurança, porque o invasor pode apenas alterar os bytes que você excluiu.
precisa saber é o seguinte
2
+1 Essa é uma excelente ideia quando você está realizando uma comparação individual. Infelizmente, estou usando o hash MD5 como um índice para procurar arquivos exclusivos entre muitas duplicatas (verificações de muitos para muitos).
Nathan Goings
1
@ b.kiener Nenhum byte é excluído. Você o entendeu mal.
Soroush Falahati 23/01/19
47

Como Anton Gogolev observou , o FileStream lê 4096 bytes por vez por padrão, mas você pode especificar qualquer outro valor usando o construtor FileStream:

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

Observe que Brad Abrams, da Microsoft, escreveu em 2004:

não há benefício em envolver um BufferedStream em torno de um FileStream. Copiamos a lógica de buffer do BufferedStream no FileStream cerca de 4 anos atrás para incentivar um melhor desempenho padrão

fonte

Tal Aloni
fonte
22

Invoque a porta do Windows do md5sum.exe . É cerca de duas vezes mais rápido que a implementação do .NET (pelo menos na minha máquina usando um arquivo de 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 ();
}
Christian Birkl
fonte
3
WOW - usando md5sums.exe de pc-tools.net/win32/md5sums torna muito rápido. 1681457152 bytes, 8672 ms = 184,91 MB / s -> 1,6 GB ~ 9 segundos Isso será rápido o suficiente para o meu propósito.
crono 24/07/2009
16

Ok - obrigado a todos - deixe-me concluir:

  1. o uso de um exe "nativo" para fazer o hash demorou de 6 minutos a 10 segundos, o que é enorme.
  2. Aumentar o buffer foi ainda mais rápido - o arquivo de 1,6 GB demorou 5,2 segundos usando o MD5 no .Net, então irei com esta solução - obrigado novamente
crono
fonte
10

Eu fiz testes com tamanho de buffer, executando este código

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 eu testei com um arquivo de 29½ GB de tamanho, os resultados foram

  • 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,22s ao usar o código original sem buffer.

Estou executando uma CPU i5 2500K, 12 GB de RAM e uma unidade SSD OCZ Vertex 4 256 GB.

Então eu pensei, que tal um disco rígido padrão de 2 TB. E os resultados foram assim

  • 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 para nenhum buffered 368,24

Então, eu recomendaria nenhum buffer ou um buffer de no máximo 1 moinho.

Anders
fonte
Eu não entendo. Como esse teste pode contradizer a resposta aceita de Anton Gogolev?
buddybubble
Você pode adicionar uma descrição de cada campo nos seus dados?
videoguy 28/09/15
2

Você está fazendo algo errado (provavelmente um buffer de leitura muito pequeno). Em uma máquina com idade indecente (Athlon 2x1800MP de 2002) que possui DMA no disco provavelmente fora de sintonia (6.6M / s é muito lento ao fazer leituras sequenciais):

Crie um arquivo 1G com dados "aleatórios":

# 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

Isso também é estranho, o md5 é consistentemente mais lento que o sha1 para mim (execute novamente várias vezes).

Pasi Savolainen
fonte
Sim - tentarei aumentar o buffer - como Anton Gogolev sugeriu. Eu o executei através de um MD5.exe "nativo", que levou 9 segundos com um arquivo de 1,6 GB.
crono 24/07/2009
2

Sei que estou atrasado para a festa, mas realizei o teste antes de realmente implementar a solução.

Eu realizei teste contra a classe MD5 embutida e também o md5sum.exe . No meu caso, a classe incorporada levou 13 segundos, enquanto o md5sum.exe também ficou em torno de 16 a 18 segundos em cada execução.

    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
fonte