Apa cara tercepat untuk membuat checksum untuk file besar di C #

128

Saya harus menyinkronkan file besar di beberapa mesin. Ukuran file bisa hingga 6GB. Sinkronisasi akan dilakukan secara manual setiap beberapa minggu. Saya tidak dapat mempertimbangkan nama file karena mereka dapat berubah kapan saja.

Rencana saya adalah membuat checksum pada PC tujuan dan pada PC sumber dan kemudian menyalin semua file dengan checksum, yang belum ada di tujuan, ke tujuan. Upaya pertama saya adalah seperti ini:

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

Masalahnya adalah runtime:
- dengan SHA256 dengan File 1,6 GB -> 20 menit
- dengan MD5 dengan File 1,6 GB -> 6,15 menit

Apakah ada cara yang lebih baik - lebih cepat - untuk mendapatkan checksum (mungkin dengan fungsi hash yang lebih baik)?

krono
sumber
2
Apakah Anda benar-benar perlu memeriksa Checksum? Bagaimana Anda menyalin file? Jika pada windows Anda, saya akan menggunakan Robocopy versi terbaru ...
Mesh
6
Tip yang bagus di sini hanya untuk mengganggu hashing jika ukuran file berbeda antara 2 file kandidat stackoverflow.com/a/288756/74585
Matthew Lock

Jawaban:

117

Masalahnya di sini adalah yang SHA256Managedmembaca 4096 byte pada suatu waktu (mewarisi dari FileStreamdan menimpaRead(byte[], int, int) untuk melihat berapa banyak yang dibaca dari filestream), yang merupakan buffer terlalu kecil untuk disk IO.

Untuk mempercepat hal-hal (2 menit untuk hashing berkas 2 Gb pada mesin dengan SHA256, 1 menit untuk MD5) bungkus FileStreamdi BufferedStreamdan set berukuran cukup ukuran buffer (saya mencoba dengan ~ 1 Mb buffer):

// 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
sumber
3
OK - ini membuat diffence yang - hashing file 1.6GB dengan MD5 mengambil 5,2 detik di kotak saya (QuadCode @ 2,6 GHz, 8GB Ram) - bahkan lebih cepat sebagai implementaion asli ...
crono
4
saya tidak mengerti. Saya baru saja mencoba saran ini tetapi perbedaannya minimal hingga tidak ada sama sekali. File 1024mb tanpa buffering 12-14 detik, dengan buffering juga 12-14 detik - saya mengerti bahwa membaca ratusan blok 4k akan menghasilkan lebih banyak IO tetapi saya bertanya pada diri sendiri apakah kerangka kerja atau API asli di bawah kerangka tidak menangani hal ini sudah ..
Christian Casutt
11
Sedikit terlambat ke pesta, tetapi untuk FileStreams tidak perlu lagi membungkus aliran dalam BufferedStream karena saat ini sudah dilakukan di FileStream itu sendiri. Sumber
Reyhn
Saya baru saja mengalami masalah ini dengan file yang lebih kecil (<10MB, tetapi butuh waktu lama untuk mendapatkan MD5). Meskipun saya menggunakan .Net 4.5, beralih ke metode ini dengan BufferedStream memotong waktu hash dari sekitar 8,6 detik menjadi <300 ms untuk file
8,6MB
Saya menggunakan BufferedStream / w 512 kB, bukannya 1024 kB. File 1,8 GB diselesaikan dalam 30 detik.
Hugo Woesthuis
61

Jangan checksum seluruh file, buat checksum setiap 100mb, jadi setiap file memiliki koleksi checksum.

Kemudian ketika membandingkan checksum, Anda dapat berhenti membandingkan setelah checksum berbeda pertama, keluar lebih awal, dan menyelamatkan Anda dari pemrosesan seluruh file.

Masih perlu waktu penuh untuk file yang identik.

Biner Terburuk
sumber
2
Saya suka idenya, tetapi itu tidak akan berhasil dalam skenario saya karena saya akan berakhir dengan banyak file yang tidak berubah dari waktu ke waktu.
crono
1
bagaimana Anda melakukan checksum setiap 100mb file?
Smith
1
Bukan ide yang baik ketika menggunakan checksum untuk alasan keamanan, karena penyerang hanya dapat mengubah byte yang telah Anda kecualikan.
b.kiener
2
+1 Ini adalah ide bagus ketika Anda melakukan perbandingan satu-ke-satu. Sayangnya, saya menggunakan hash MD5 sebagai indeks untuk mencari file unik di antara banyak duplikat (pemeriksaan banyak-ke-banyak).
Nathan Goings
1
@ b.kiener Tidak ada byte yang dikecualikan. Anda salah paham.
Soroush Falahati
47

Seperti yang dicatat Anton Gogolev , FileStream secara default membaca 4096 byte, tetapi Anda bisa menentukan nilai lain menggunakan konstruktor FileStream:

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

Perhatikan bahwa Brad Abrams dari Microsoft menulis pada tahun 2004:

tidak ada manfaat dari membungkus BufferedStream di sekitar FileStream. Kami menyalin logika buffering BufferedStream ke FileStream sekitar 4 tahun yang lalu untuk mendorong kinerja default yang lebih baik

sumber

Tal Aloni
sumber
22

Aktifkan port windows dari md5sum.exe . Ini sekitar dua kali lebih cepat dari implementasi .NET (setidaknya pada komputer saya menggunakan file 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
sumber
3
WOW - menggunakan md5sums.exe dari pc-tools.net/win32/md5sums membuatnya sangat cepat. 1681457152 byte, 8672 ms = 184,91 MB / detik -> 1,6GB ~ 9 detik Ini akan cukup cepat untuk tujuan saya.
crono
16

Oke - terima kasih untuk Anda semua - izinkan saya menyelesaikannya:

  1. menggunakan exe "asli" untuk melakukan hashing membutuhkan waktu dari 6 Menit hingga 10 Detik yang sangat besar.
  2. Meningkatkan buffer bahkan lebih cepat - file 1.6GB butuh 5,2 detik menggunakan MD5 di .Net, jadi saya akan menggunakan solusi ini - terima kasih lagi
krono
sumber
10

Saya melakukan tes dengan ukuran buffer, menjalankan kode ini

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

Dan saya menguji dengan file berukuran 29½ GB, hasilnya

  • 10.000: 369.224
  • 100.000: 362,55s
  • 1.000.000: 361,53d
  • 10.000.000: 434.15d
  • 100.000.000: 435.15d
  • 1.000.000.000: 434,31d
  • Dan 376.222 ketika menggunakan kode asli, tidak ada buffered.

Saya menjalankan CPU i5 2500K, ram 12 GB, dan drive SSD OCZ Vertex 4 256 GB.

Jadi saya pikir, bagaimana dengan hard drive 2TB standar. Dan hasilnya seperti ini

  • 10.000: 368,52s
  • 100.000: 364.15d
  • 1.000.000: 363,06 dtk
  • 10.000.000: 678,96s
  • 100.000.000: 617,89s
  • 1.000.000.000: 626,86s
  • Dan untuk tidak ada buffer 368,24

Jadi saya akan merekomendasikan tidak ada buffer atau buffer max 1 mill.

Anders
sumber
Saya tidak mengerti. Bagaimana tes ini bertentangan dengan jawaban yang diterima dari Anton Gogolev?
buddybubble
Bisakah Anda menambahkan deskripsi setiap bidang dalam data Anda?
videoguy
2

Anda melakukan sesuatu yang salah (mungkin buffer baca terlalu kecil). Pada mesin usia yang tidak senonoh (Athlon 2x1800MP dari 2002) yang memiliki DMA pada disk mungkin rusak (6,6M / s sangat lambat ketika melakukan pembacaan berurutan):

Buat file 1G dengan data "acak":

# 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.299d

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

1m58.832s

Ini juga aneh, MD5 secara konsisten lebih lambat daripada SHA1 bagi saya (reran beberapa kali).

Pasi Savolainen
sumber
Ya - Saya akan mencoba meningkatkan buffer - seperti Anton Gogolev yang disarankan. Saya menjalankannya melalui "asli" MD5.exe yang membutuhkan waktu 9 detik dengan file 1,6 GB.
crono
2

Saya tahu bahwa saya terlambat ke pesta tetapi melakukan tes sebelum benar-benar menerapkan solusi.

Saya melakukan tes terhadap kelas MD5 inbuilt dan juga md5sum.exe . Dalam kasus saya, kelas inbuilt mengambil 13 detik di mana md5sum.exe juga sekitar 16-18 detik dalam setiap menjalankan.

    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
sumber