Cách nhanh nhất để tạo tổng kiểm tra cho các tệp lớn trong C # là gì


128

Tôi phải đồng bộ các tệp lớn trên một số máy. Các tập tin có thể có kích thước lên tới 6GB. Việc đồng bộ sẽ được thực hiện thủ công cứ sau vài tuần. Tôi không thể xem xét tên tệp vì chúng có thể thay đổi bất cứ lúc nào.

Kế hoạch của tôi là tạo tổng kiểm tra trên PC đích và trên PC nguồn và sau đó sao chép tất cả các tệp có tổng kiểm tra chưa có ở đích đến đích. Nỗ lực đầu tiên của tôi là một cái gì đó như thế này:

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

Vấn đề là thời gian chạy:
- với SHA256 với Tệp 1,6 GB -> 20 phút
- với MD5 với Tệp 1,6 GB -> 6,15 phút

Có cách nào tốt hơn - nhanh hơn - để có được tổng kiểm tra (có thể với hàm băm tốt hơn) không?


2
Bạn có thực sự cần kiểm tra Tổng kiểm tra? Làm thế nào bạn sao chép các tập tin? Nếu trên cửa sổ của bạn, tôi sẽ sử dụng phiên bản Robocopy mới nhất ...
Lưới

6
Mẹo hay ở đây chỉ làm phiền băm nếu kích thước tệp khác nhau giữa 2 tệp ứng viên stackoverflow.com/a/288756/74585
Matthew Lock

Câu trả lời:


117

Vấn đề ở đây là SHA256Managedđọc 4096 byte cùng một lúc (kế thừa từ FileStreamvà ghi đè Read(byte[], int, int)để xem nó đọc được bao nhiêu từ filestream), đây là bộ đệm quá nhỏ cho IO đĩa.

Để mọi thứ tăng tốc (2 phút để băm tập tin 2 Gb trên máy tính của tôi với SHA256, 1 phút cho MD5) bọc FileStreamtrong BufferedStreamvà thiết lập kích thước bộ đệm một cách hợp lý có kích thước (Tôi đã thử với ~ 1 Mb đệm):

// 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
OK - điều này tạo ra sự khác biệt - băm tệp 1.6GB với MD5 mất 5,2 giây trên hộp của tôi (QuadCode @ 2,6 GHz, 8GB Ram) - thậm chí còn nhanh hơn khi triển khai riêng ...
crono

4
tôi không hiểu tôi chỉ thử đề nghị này nhưng sự khác biệt là tối thiểu không có gì. Tệp 1024mb không đệm 12-14 giây, với bộ đệm cũng 12-14 giây - tôi hiểu rằng việc đọc hàng trăm khối 4k sẽ tạo ra nhiều IO hơn nhưng tôi tự hỏi liệu khung hoặc API gốc bên dưới khung không xử lý được điều này chưa ..
Christian Casutt

11
Đến bữa tiệc muộn một chút, nhưng đối với FileStreams, không còn cần phải gói luồng trong BufferedStream vì ngày nay nó đã được thực hiện trong chính FileStream. Nguồn
Reyhn 2/11/2016

Tôi vừa mới trải qua vấn đề này với các tệp nhỏ hơn (<10 MB, nhưng mất mãi mãi để có MD5). Mặc dù tôi sử dụng .Net 4.5, việc chuyển sang phương thức này với BufferedStream đã giảm thời gian băm xuống từ khoảng 8,6 giây xuống <300 ms cho một tệp
8,6 MB

Tôi đã sử dụng BufferedStream / w 512 kB thay vì 1024 kB. Tệp 1,8 GB đã được giải quyết trong 30 giây.
Hugo Woesthuis

61

Không kiểm tra toàn bộ tệp, tạo tổng kiểm mỗi 100mb hoặc hơn, vì vậy mỗi tệp có một bộ tổng kiểm tra.

Sau đó, khi so sánh tổng kiểm tra, bạn có thể ngừng so sánh sau lần kiểm tra khác nhau đầu tiên, ra sớm và tiết kiệm cho bạn khỏi việc xử lý toàn bộ tệp.

Nó vẫn sẽ mất toàn bộ thời gian cho các tệp giống hệt nhau.


2
Tôi thích ý tưởng này, nhưng nó sẽ không hoạt động trong kịch bản của tôi bởi vì tôi sẽ kết thúc với rất nhiều tệp không thay đổi theo thời gian.
crono

1
Làm thế nào để bạn kiểm tra mỗi 100mb của một tập tin?
Smith

1
Không phải là một ý tưởng tốt khi sử dụng tổng kiểm tra vì lý do bảo mật, bởi vì kẻ tấn công chỉ có thể thay đổi các byte mà bạn đã loại trừ.
b.kiener

2
+1 Đây là một ý tưởng tuyệt vời khi bạn thực hiện so sánh một đối một. Thật không may, tôi đang sử dụng hàm băm MD5 làm chỉ mục để tìm kiếm các tệp duy nhất trong số nhiều bản sao (nhiều kiểm tra nhiều-nhiều).
Chuyến đi của Nathan

1
@ b.kiener Không có byte được loại trừ. Bạn hiểu lầm anh.
Soroush Falahati

47

Như Anton Gogolev đã lưu ý , FileStream đọc 4096 byte theo mặc định, nhưng bạn có thể chỉ định bất kỳ giá trị nào khác bằng cách sử dụng hàm tạo FileStream:

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

Lưu ý rằng Brad Abrams từ Microsoft đã viết vào năm 2004:

không có lợi ích gì từ việc gói BufferedStream xung quanh FileStream. Chúng tôi đã sao chép logic đệm của BufferedStream vào FileStream khoảng 4 năm trước để khuyến khích hiệu suất mặc định tốt hơn

nguồn


22

Gọi cổng windows của md5sum.exe . Nó nhanh gấp hai lần so với triển khai .NET (ít nhất là trên máy của tôi sử dụng tệp 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 ();
}

3
WOW - sử dụng md5sums.exe từ pc-tools.net/win32/md5sums làm cho nó thực sự nhanh chóng. 1681456152 byte, 8672 ms = 184,91 MB / giây -> 1,6GB ~ 9 giây Điều này sẽ đủ nhanh cho mục đích của tôi.
crono

16

Ok - cảm ơn tất cả các bạn - hãy để tôi gói lại:

  1. sử dụng exe "bản địa" để thực hiện băm mất thời gian từ 6 phút đến 10 giây là rất lớn.
  2. Việc tăng bộ đệm thậm chí còn nhanh hơn - tệp 1.6GB mất 5,2 giây khi sử dụng MD5 trong .Net, vì vậy tôi sẽ đi với giải pháp này - cảm ơn một lần nữa

10

Tôi đã thử nghiệm với kích thước bộ đệm, chạy mã này

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

Và tôi đã thử nghiệm với một tệp có kích thước 29 GB, kết quả là

  • 10.000: 369,24s
  • 100.000: 362,55 giây
  • 1.000.000: 361,53 giây
  • 10.000.000: 434,15s
  • 100.000.000: 435,15s
  • 1.000.000.000: 434,31s
  • Và 376,22s khi sử dụng mã gốc, không có mã đệm.

Tôi đang chạy CPU i5 2500K, ram 12 GB và ổ SSD OCZ Vertex 4 256 GB.

Vì vậy, tôi nghĩ, những gì về một ổ cứng 2TB tiêu chuẩn. Và kết quả là như thế này

  • 10.000: 368,52 giây
  • 100.000: 364,15s
  • 1.000.000: 363,06
  • 10.000.000: 678,96 giây
  • 100.000.000: 617.889
  • 1.000.000.000: 626,86s
  • Và không có bộ đệm nào 368,24

Vì vậy, tôi khuyên bạn không nên có bộ đệm hoặc bộ đệm tối đa 1 mill.


Tôi không hiểu Làm thế nào bài kiểm tra này có thể mâu thuẫn với câu trả lời được chấp nhận từ Anton Gogolev?
thân

Bạn có thể thêm mô tả của từng lĩnh vực trong dữ liệu của bạn?
videoguy

2

Bạn đang làm gì đó sai (có thể là quá nhỏ đọc bộ đệm). Trên một máy có độ tuổi không ổn định (Athlon 2x1800MP từ 2002) có DMA trên đĩa có thể đã hết (6,6M / giây bị chậm khi thực hiện đọc tuần tự):

Tạo tệp 1G với dữ liệu "ngẫu nhiên":

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

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

1m58.832s

Điều này cũng kỳ lạ, md5 luôn chậm hơn sha1 đối với tôi (reran nhiều lần).


Có - tôi sẽ cố gắng tăng bộ đệm - giống như Anton Gogolev có đường. Tôi đã chạy nó thông qua MD5.exe "bản địa", mất 9 giây với một tệp 1,6 GB.
crono

2

Tôi biết rằng tôi đến dự tiệc muộn nhưng đã thực hiện kiểm tra trước khi thực sự thực hiện giải pháp.

Tôi đã thực hiện kiểm tra đối với lớp MD5 sẵn có và cả md5sum.exe . Trong trường hợp của tôi, lớp inbuilt mất 13 giây trong đó md5sum.exe cũng chỉ khoảng 16-18 giây trong mỗi lần chạy.

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.