Какой самый быстрый способ создать контрольную сумму для больших файлов в C # - PullRequest
125 голосов
/ 24 июля 2009

Я должен синхронизировать большие файлы на некоторых машинах. Файлы могут быть размером до 6 ГБ. Синхронизация будет выполняться вручную каждые несколько недель. Я не могу принять во внимание имя файла, потому что они могут измениться в любое время.

Мой план состоит в том, чтобы создать контрольные суммы на конечном компьютере и на исходном компьютере, а затем скопировать все файлы с контрольной суммой, которых еще нет в месте назначения, в место назначения. Моя первая попытка была примерно такой:

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

Проблема была во время выполнения:
- с SHA256 с файлом объемом 1,6 ГБ -> 20 минут
- с MD5 с 1,Файл 6 ГБ -> 6,15 минут

Есть ли лучший - более быстрый - способ получения контрольной суммы (возможно, с лучшей хэш-функцией)?

Ответы [ 8 ]

110 голосов
/ 24 июля 2009

Проблема здесь в том, что SHA256Managed читает 4096 байт за раз (наследует от FileStream и переопределяет Read(byte[], int, int), чтобы увидеть, сколько он читает из файлового потока), что слишком мало для буфера для дискового ввода-вывода.

Чтобы ускорить процесс (2 минуты для хеширования файла 2 Гб на моем компьютере с SHA256, 1 минута для MD5), оберните FileStream в BufferedStream и установите размер буфера разумного размера (я пытался с ~ 1 Мббуфер):

// Not sure if BufferedStream should be wrapped in using block
using(var stream = new BufferedStream(File.OpenRead(filePath), 1200000))
{
    // The rest remains the same
}
61 голосов
/ 24 июля 2009

Не проверяйте контрольную сумму всего файла, создавайте контрольные суммы каждые 100 МБ или около того, чтобы у каждого файла была коллекция контрольных сумм.

Затем при сравнении контрольных сумм вы можете прекратить сравнение после первой другой контрольной суммы, выходяна раннем этапе и избавит вас от обработки всего файла.

Для идентичных файлов все равно потребуется полное время.

44 голосов
/ 17 января 2015

Как отметил Антон Гоголев , FileStream читает по умолчанию 4096 байт за раз, но вы можете указать любое другое значение, используя конструктор FileStream:

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

Обратите внимание, что Брэд Абрамс изВ 2004 году Microsoft написала:

Нет никакой выгоды от наложения BufferedStream на FileStream. Мы скопировали логику буферизации BufferedStream в FileStream около 4 лет назад, чтобы повысить производительность по умолчанию

source

22 голосов
/ 24 июля 2009

Вызвать порт Windows md5sum.exe . Это примерно в два раза быстрее, чем реализация .NET (по крайней мере, на моем компьютере, использующем файл объемом 1,2 ГБ)

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 ();
}
16 голосов
/ 24 июля 2009

Хорошо - спасибо всем - позвольте мне обернуть это:

  1. с использованием "нативного" exe для хеширования потребовалось время от 6 минут до 10 секундчто огромно.
  2. Увеличение буфера было еще быстрее - файл 1,6 ГБ занял 5,2 секунды с использованием MD5 в .Net, поэтому я пойду с этим решением - еще раз спасибо
10 голосов
/ 07 октября 2012

Я сделал тесты с размером буфера, запустив этот код

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

И я проверил с файлом размером 29½ ГБ, результаты были

  • 10.000: 369,24 с
  • 100.000: 362,55 с
  • 1.000.000: 361,53 с
  • 10.000.000: 434,15 с
  • 100 000 000: 435,15 с
  • 1.000.000.000: 434,31 с
  • и 376,22 с при использовании исходного кода без буферизации.

Я использую процессор i5 2500K,Оперативная память 12 ГБ и SSD-накопитель OCZ Vertex 4 256 ГБ.

Я подумал, что насчет стандартного жесткого диска емкостью 2 ТБ. И результаты были такими:

  • 10.000: 368,52 с
  • 100.000: 364,15 с
  • 1.000.000: 363,06 с
  • 10.000.000: 678,96s
  • 100.000.000: 617,89s
  • 1.000.000.000: 626,86s
  • И ни для одного буферизованного 368,24

Поэтому я бы порекомендовал либо без буфера, либо с буфером до 1 миллиметра.

2 голосов
/ 24 июля 2009

Вы делаете что-то не так (возможно, слишком маленький буфер чтения). На компьютере несовершеннолетнего возраста (Athlon 2x1800MP от 2002 года), в котором DMA на диске, вероятно, неисправен (6,6 М / с, чертовски медленно при последовательном чтении):

Создание файла 1G со «случайными» данными:

# 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

Это также странно, md5последовательно медленнее, чем sha1 для меня (перезапустить несколько раз).

1 голос
/ 16 марта 2019

Я знаю, что опоздал на вечеринку, но выполнил тестирование, прежде чем фактически реализовать решение.

Я выполнил тест для встроенного класса MD5, а также md5sum.exe . В моем случае встроенный класс занял 13 секунд, где md5sum.exe тоже примерно 16-18 секунд при каждом запуске.

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