C # 'da büyük dosyalar için bir sağlama toplamı oluşturmanın en hızlı yolu nedir


129

Bazı makinelerde büyük dosyaları senkronize etmem gerekiyor. Dosyaların boyutu 6 GB'a kadar olabilir. Senkronizasyon birkaç haftada bir manuel olarak yapılacaktır. Dosya adını dikkate alamıyorum çünkü her an değişebilirler.

Planım, hedef bilgisayarda ve kaynak bilgisayarda sağlama toplamları oluşturmak ve ardından hedefte olmayan tüm dosyaları bir sağlama toplamı ile hedefe kopyalamaktır. İlk denemem şuna benzer bir şeydi:

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

Sorun çalışma
zamanıydı : - 1,6 GB Dosyalı SHA256 ile -> 20 dakika
- 1,6 GB Dosyalı MD5 ile -> 6,15 dakika

Sağlama toplamını almanın daha iyi - daha hızlı - bir yolu var mı (belki daha iyi bir hash işlevi ile)?


2
Sağlama toplamını gerçekten kontrol etmeniz gerekiyor mu? Dosyaları nasıl kopyalıyorsunuz? Windows kullanıyorsanız, Robocopy'nin en son sürümünü kullanırdım ...
Mesh

6
Yalnızca dosya boyutları 2 aday dosya arasında farklıysa hashing işlemini rahatsız etmek için güzel bir ipucu stackoverflow.com/a/288756/74585
Matthew Lock

Yanıtlar:


117

Buradaki sorun SHA256Managed, bir seferde 4096 baytı okumaktır (dosya akışından ne kadar okuduğunu görmek için devralmak FileStreamve geçersiz kılmak Read(byte[], int, int)), ki bu, disk IO için çok küçük bir arabellektir.

Hız şeyler yukarı sarma (2 benim SHA256 ile makine, MD5 için 1 dakika 2 Gb dosyası karıştırmak için dakika) FileStreamiçinde BufferedStreamve set makul büyüklükte tampon boyutu (I ~ 1 Mb tamponu ile çalıştı):

// 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
Tamam - bu fark yarattı - MD5 ile 1.6GB dosyaya hashing uygulamak kutumda 5.2 saniye sürdü (QuadCode @ 2.6 GHz, 8GB Ram) - yerel uygulama kadar daha hızlı ...
crono

4
anlamadım. Bu öneriyi yeni denedim ama fark çok az. 12-14 saniye ara belleğe alma olmadan 1024mb dosya, ayrıca 12-14 saniye ara belleğe alma - yüzlerce 4k bloğu okumanın daha fazla GÇ üreteceğini anlıyorum, ancak çerçevenin altındaki çerçevenin veya yerel API'lerin bunu halihazırda işlemediğini kendime soruyorum ..
Christian Casutt

11
Partiye biraz geç kaldı, ancak FileStreams için, artık FileStream'in kendisinde zaten yapıldığı için akışı bir BufferedStream'e sarmaya gerek yok. Kaynak
Reyhn

Bu sorunu daha küçük dosyalarla çalışıyordum (<10MB, ancak bir MD5 almak sonsuza kadar sürdü). .Net 4.5 kullansam da, BufferedStream ile bu yönteme geçmek, hash süresini 8.6
MB'lik

1024 kB yerine BufferedStream / w 512 kB kullandım. 1.8 GB dosya 30 saniyede çözüldü.
Hugo Woesthuis

61

Dosyanın tamamını sağlama toplamı yapmayın, her 100 MB'de bir sağlama toplamı oluşturun, böylece her dosyanın bir sağlama toplamı koleksiyonu olur.

Daha sonra, sağlama toplamlarını karşılaştırırken, ilk farklı sağlama toplamından sonra karşılaştırmayı bırakabilir, erken çıkıp sizi tüm dosyayı işlemekten kurtarabilirsiniz.

Yine de aynı dosyalar için tam zaman alacak.


2
Fikri beğendim, ancak senaryomda işe yaramayacak çünkü zamanla birçok değişmemiş dosyayla karşılaşacağım.
crono

1
Bir dosyanın her 100mb'sini nasıl kontrol edersiniz?
Smith

1
Güvenlik nedenleriyle sağlama toplamı kullanırken iyi bir fikir değildir, çünkü saldırgan hariç tuttuğunuz baytları değiştirebilir.
b.kiener

2
+1 Bu, bire bir karşılaştırma yaparken mükemmel bir fikirdir. Ne yazık ki, MD5 hashini birçok yinelenen (çoktan çoğa kontrol) arasında benzersiz dosyaları aramak için bir dizin olarak kullanıyorum.
Nathan Goings

1
@ b.kiener Hiçbir bayt hariç tutulmaz. Onu yanlış anladın.
Soroush Falahati

47

Anton Gogolev'ün belirttiği gibi , FileStream bir seferde varsayılan olarak 4096 bayt okur, ancak FileStream yapıcısını kullanarak başka herhangi bir değer belirtebilirsiniz:

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

Microsoft'tan Brad Abrams'ın 2004'te yazdığına dikkat edin:

Bir Tamponlu Akışın bir FileStream etrafına sarılmasının sıfır faydası vardır. Daha iyi varsayılan performansı teşvik etmek için BufferedStream'in arabelleğe alma mantığını FileStream'e yaklaşık 4 yıl önce kopyaladık

kaynak


22

Md5sum.exe'nin Windows bağlantı noktasını çağırın . NET uygulamasından yaklaşık iki kat daha hızlıdır (en azından makinemde 1,2 GB dosya kullanarak)

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 - pc-tools.net/win32/md5sums adresinden md5sums.exe'yi kullanmak, gerçekten hızlı hale getirir. 1681457152 bayt, 8672 ms = 184,91 MB / sn -> 1,6 GB ~ 9 saniye Bu, amacım için yeterince hızlı olacak.
crono

16

Tamam - hepinize teşekkürler - şunu bitireyim:

  1. Hashing yapmak için "yerel" bir exe kullanmak, 6 dakikadan 10 saniyeye kadar zaman aldı ve bu çok büyük bir şeydi.
  2. Arabelleği artırmak daha da hızlıydı - 1.6GB dosya .Net'te MD5 kullanıldığında 5.2 saniye sürdü, bu yüzden bu çözüme devam edeceğim - tekrar teşekkürler

10

Bu kodu çalıştırarak arabellek boyutu ile testler yaptım

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

Ve 29½ GB boyutunda bir dosya ile test ettim, sonuçlar

  • 10.000: 369,24'ler
  • 100.000: 362,55'ler
  • 1.000.000: 361,53s
  • 10.000.000: 434,15 sn.
  • 100.000.000: 435,15 sn.
  • 1.000.000.000: 434,31s
  • Ve orijinali kullanırken 376,22s, arabelleğe alınmamış kod yok.

Bir i5 2500K CPU, 12 GB ram ve OCZ Vertex 4 256 GB SSD sürücüsü kullanıyorum.

Bu yüzden standart bir 2 TB sabit sürücüye ne dersiniz diye düşündüm. Ve sonuçlar böyleydi

  • 10.000: 368,52'ler
  • 100.000: 364,15 sn.
  • 1.000.000: 363,06sn
  • 10.000.000: 678,96 sn.
  • 100.000.000: 617,89
  • 1.000.000.000: 626,86
  • Ve hiçbiri arabelleğe alınmamış 368,24

Bu yüzden tampon yok veya maksimum 1 millik bir tampon tavsiye ederim.


Ben anlamadım. Bu test Anton Gogolev'in kabul ettiği yanıtla nasıl çelişebilir?
buddybubble

Verilerinizdeki her alanın açıklamasını ekleyebilir misiniz?
videoguy

2

Yanlış bir şey yapıyorsunuz (muhtemelen çok küçük okuma tamponu). Diskte büyük olasılıkla DMA'ya sahip olan yetersiz yaştaki bir makinede (2002'den Athlon 2x1800MP) (sıralı okumalar yaparken 6.6M / s çok yavaş):

"Rastgele" veriler içeren bir 1G dosyası oluşturun:

# 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

Bu da tuhaf, md5 benim için sha1'den sürekli olarak daha yavaş (birkaç kez tekrarlayın).


Evet - Anton Gogolev'ün önerdiği gibi tamponu artırmaya çalışacağım. 1,6 GB'lık bir dosyayla 9 saniye süren "yerel" bir MD5.exe aracılığıyla çalıştırdım.
crono

2

Partiye geç kaldığımı ama çözümü gerçekten uygulamadan önce test yaptığımı biliyorum.

Dahili MD5 sınıfına ve ayrıca md5sum.exe'ye karşı test yaptım . Benim durumumda dahili sınıf 13 saniye sürdü, burada md5sum.exe her çalıştırmada 16-18 saniye civarında.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.