Каков самый быстрый способ создания контрольной суммы для больших файлов в С#

Я должен синхронизировать большие файлы на некоторых машинах. Файлы могут быть размером до 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 минут

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

Ответ 1

Проблема заключается в том, что 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
}

Ответ 2

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

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

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

Ответ 3

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

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

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

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

источник

Ответ 4

Вызовите порт 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 ();
}

Ответ 5

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

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

Ответ 6

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

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,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
  • И 376,22 с при использовании исходного, без буферизированного кода.

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

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

  • 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
  • И для ни одного буферизированного 368,24

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

Ответ 7

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

Создайте 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 для меня (повтор несколько раз).

Ответ 8

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

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

    DateTime current = DateTime.Now;
    string file = @"C:\text.iso";//It 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);
        }
    }