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

129

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

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

Хроно
источник
2
Вам действительно нужно проверить контрольную сумму? Как вы копируете файлы? Если у вас Windows, я бы использовал последнюю версию Robocopy ...
Mesh
6
Хороший совет здесь, чтобы беспокоиться о хешировании только в том случае, если размеры файлов различаются между двумя файлами-кандидатами stackoverflow.com/a/288756/74585
Мэтью Лок

Ответы:

117

Проблема здесь в том, что 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
}
Антон Гоголев
источник
3
Хорошо - это сделало разницу - хеширование файла 1,6 ГБ с помощью MD5 на моем компьютере заняло 5,2 секунды (QuadCode @ 2,6 ГГц, 8 ГБ ОЗУ) - даже быстрее, чем собственная реализация ...
crono
4
я не понимаю. Я просто попробовал это предложение, но разница минимальна. Файл 1024 МБ без буферизации 12–14 секунд, с буферизацией также 12–14 секунд - я понимаю, что чтение сотен блоков размером 4 КБ приведет к увеличению ввода-вывода, но я спрашиваю себя, не справляются ли уже с этим фреймворк или собственные API под фреймворком ..
Christian Casutt
11
Немного поздно для вечеринки, но для FileStreams больше нет необходимости оборачивать поток в BufferedStream, как это в настоящее время уже сделано в самом FileStream. Источник
Reyhn 02
Я просто столкнулся с этой проблемой с файлами меньшего размера (<10 МБ, но на получение MD5 ушла целая вечность). Несмотря на то, что я использую .Net 4.5, переключение на этот метод с помощью BufferedStream сократило время хеширования примерно с 8,6 секунд до <300 мс для файла
8,6
Я использовал BufferedStream / w 512 кБ вместо 1024 кБ. Файл 1,8 ГБ был решен за 30 секунд.
Hugo Woesthuis
61

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

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

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

Бинарная тревога
источник
2
Мне нравится эта идея, но она не сработает в моем сценарии, потому что со временем у меня останется много неизмененных файлов.
crono
1
как вы подсчитываете контрольную сумму каждые 100 МБ файла?
Smith
1
Не рекомендуется использовать контрольную сумму по соображениям безопасности, потому что злоумышленник может просто изменить те байты, которые вы исключили.
b.kiener 08
2
+1 Это отличная идея, когда вы проводите однозначное сравнение. К сожалению, я использую хеш MD5 в качестве индекса для поиска уникальных файлов среди множества дубликатов (проверки «многие ко многим»).
Натан Гоингс,
1
@ b.kiener Ни один байт не исключается. Вы неправильно его поняли.
Соруш
47

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

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

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

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

источник

Тал Алони
источник
22

Вызовите порт 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 ();
}
Кристиан Биркл
источник
3
Ничего себе - использование md5sums.exe с pc-tools.net/win32/md5sums делает это действительно быстро. 1681457152 байта, 8672 мс = 184,91 МБ / с -> 1,6 ГБ ~ 9 секунд Это будет достаточно быстро для моей цели.
crono
16

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

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

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

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,96 с
  • 100.000.000: 617,89 с
  • 1.000.000.000: 626,86 с
  • И ни для кого не буферизовано 368,24

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

Андерс
источник
Я не понимаю. Как этот тест может противоречить принятому ответу Антона Гоголева?
buddybubble
Можете ли вы добавить описание каждого поля в ваших данных?
видеогай
2

Вы делаете что-то не так (вероятно, слишком маленький буфер чтения). На машине незрелого возраста (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 (несколько раз перезапущен).

Паси Саволайнен
источник
Да, я попробую увеличить буфер, как и предположил Антон Гоголев. Я запустил его через «родной» MD5.exe, который занял 9 секунд с файлом размером 1,6 ГБ.
crono
2

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

Я провел тест со встроенным классом 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);
        }
    }
Ромил Кумар Джайн
источник