Який найшвидший спосіб створити контрольну суму для великих файлів у C #


128

Мені доводиться синхронізувати великі файли на деяких машинах. Файли можуть бути розміром до 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
я не розумію. Я просто спробував цю пропозицію, але різниця мінімальна ні до чого. Файл 1024mb без буферизації 12-14 секунд, а також буферизація також 12-14 секунд - я розумію, що читання сотень блоків 4k дасть більше вводу-виводу, але я запитую себе, чи рамка чи нативні API нижче рамки вже не справляються з цим ..
Крістіан Касутт

11
Трохи запізнюється на вечірку, але для FileStreams більше немає необхідності загортати потік у BufferedStream, як це вже робиться в самій FileStream. Джерело
Reyhn

Я просто переживав цю проблему з меншими файлами (<10 Мб, але вічно взяв MD5). Навіть незважаючи на те, що я використовую .Net 4.5, перехід на цей метод за допомогою BufferedStream скоротив час хешування приблизно з 8,6 секунди до <300 мс для файлу 8,6 МБ
Taegost

Я використовував BufferedStream / w 512 кБ замість 1024 кБ. Файл 1,8 ГБ було вирішено за 30 секунд.
Хьюго Вестхуїс

61

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

Тоді, порівнюючи контрольні суми, ви можете зупинити порівняння після першої різної контрольної суми, рано виходити та врятувати вас від обробки всього файлу.

Для однакових файлів це все ще займе повний час.


2
Ідея мені подобається, але вона не спрацює за моїм сценарієм, оскільки я з часом вийду з великою кількістю незмінних файлів.
crono

1
як Ви перевіряєте суму кожні 100 Мб файлу?
Сміт

1
Не дуже гарна ідея при використанні контрольної суми з міркувань безпеки, оскільки зловмисник може просто змінити ті байти, які ви виключили.
b.kiener

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. Близько 4 років тому ми скопіювали логіку буферизації BufferedStream у FileStream, щоб заохотити кращу продуктивність за замовчуванням

джерело


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
WOW - використання md5sums.exe від pc-tools.net/win32/md5sums робить це дуже швидко. 1681457152 байт, 8672 мс = 184,91 МБ / сек -> 1,6 ГБ ~ 9 секунд. Це буде досить швидко для моєї мети.
кроно

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,22s при використанні оригінального, жодного буферизованого коду.

У мене працює 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

Чи можете ви додати опис кожного поля у свої дані?
videoguy

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

1м5.299с

# time md5sum -b temp.dat
9995e1c1a704f9c1eb6ca11e7ecb7276 *temp.dat

1м58.832с

Це також дивно, md5 стабільно повільніше, ніж sha1 для мене (повторно кілька разів).


Так - я спробую збільшити буфер - як запевнив Антон Гоголєв. Я провів його через "рідний" MD5.exe, який зайняв 9 секунд з файлом 1,6 Гб.
кроно

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.