วิธีที่เร็วที่สุดในการสร้างการตรวจสอบสำหรับไฟล์ขนาดใหญ่ใน C # คืออะไร


128

ฉันต้องซิงค์ไฟล์ขนาดใหญ่ในเครื่องบางเครื่อง ไฟล์สามารถมีขนาดสูงสุด 6GB การซิงค์จะดำเนินการด้วยตนเองทุกสองสามสัปดาห์ ฉันไม่สามารถนำชื่อไฟล์มาพิจารณาเพราะพวกเขาสามารถเปลี่ยนแปลงได้ตลอดเวลา

แผนของฉันคือสร้าง checksums บนพีซีปลายทางและบนพีซีต้นทางแล้วคัดลอกไฟล์ทั้งหมดที่มี checksum ซึ่งไม่ได้อยู่ในปลายทางไปยังปลายทางแล้ว ความพยายามครั้งแรกของฉันคือสิ่งนี้:

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 GB -> 20 นาที
- ด้วย MD5 พร้อมไฟล์ 1,6 GB -> 6.15 นาที

มีวิธีที่ดีกว่า - เร็วกว่าในการรับเช็คซัม (หรืออาจใช้ฟังก์ชันแฮชที่ดีกว่า)


2
คุณต้องการตรวจสอบ Checksum หรือไม่ คุณจะคัดลอกไฟล์อย่างไร หากคุณบนหน้าต่างผมจะใช้รุ่นล่าสุดของ Robocopy ...
ตาข่าย

6
เคล็ดลับที่ดีที่นี่รบกวนการแฮ็กเท่านั้นหากขนาดไฟล์แตกต่างกันระหว่างไฟล์ที่มีตัวเลือก 2 ตัวstackoverflow.com/a/288756/74585
Matthew Lock

คำตอบ:


117

ปัญหาที่นี่คือที่SHA256Managedอ่าน 4096 ไบต์ในแต่ละครั้ง (สืบทอดมาจากFileStreamและแทนที่Read(byte[], int, int)เพื่อดูว่ามันอ่านจาก filestream) ซึ่งมีขนาดเล็กเกินไปบัฟเฟอร์สำหรับดิสก์ IO

สิ่งที่ความเร็วขึ้น (2 นาที hashing ไฟล์ 2 Gb ในเครื่องของฉันกับ SHA256 1 นาทีสำหรับ MD5) ห่อFileStreamในBufferedStreamและกำหนดขนาดของบัฟเฟอร์พอสมควรขนาด (ฉันพยายามกับ ~ 1 Mb บัฟเฟอร์):

// 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
ตกลง - นี้ทำ diffence - การ hashing ไฟล์ 1.6GB กับ MD5 เอา 5.2 วินาทีบนกล่องของฉัน (QuadCode @ 2.6 GHz, Ram 8GB) - ได้เร็วขึ้นเป็น implementaion พื้นเมือง ...
Crono

4
ฉันไม่เข้าใจ ฉันเพิ่งลองข้อเสนอแนะนี้ ไฟล์ 1024mb ที่ไม่มีบัฟเฟอร์ 12-14 วินาทีพร้อมกับบัฟเฟอร์ยัง 12-14 วินาทีด้วย - ฉันเข้าใจว่าการอ่านบล็อก 4k หลายร้อยบล็อกจะสร้าง IO มากกว่านี้ แต่ฉันถามตัวเองว่าเฟรมเวิร์กหรือ API ดั้งเดิมด้านล่างเฟรมไม่ได้จัดการเรื่องนี้แล้ว ..
Christian Casutt

11
สายไปงานเลี้ยงเล็ก ๆ น้อย ๆ แต่สำหรับ FileStreams ไม่จำเป็นต้องปิดสตรีมใน BufferedStream อีกต่อไปเพราะทุกวันนี้ทำใน FileStream ไปแล้ว ที่มา
Reyhn

ฉันเพิ่งจะผ่านปัญหานี้ด้วยไฟล์ขนาดเล็ก (<10MB แต่ใช้เวลาตลอดไปเพื่อรับ MD5) แม้ว่าฉันจะใช้. Net 4.5 แต่การสลับไปใช้วิธีนี้ด้วย BufferedStream จะลดเวลาการแฮชลงจาก 8.6 วินาทีเป็น <300 มิลลิวินาทีสำหรับไฟล์ 8.6MB
Taegost

ฉันใช้ BufferedStream / w 512 kB แทน 1024 kB ไฟล์ 1.8 GB แก้ไขได้ใน 30 วินาที
Hugo Woesthuis

61

อย่าตรวจสอบไฟล์ทั้งหมดสร้าง checksums ทุก ๆ 100mb หรือมากกว่านั้นดังนั้นไฟล์แต่ละไฟล์จึงมีชุดของ checksums

จากนั้นเมื่อเปรียบเทียบ checksums คุณสามารถหยุดเปรียบเทียบหลังจาก checksum ที่แตกต่างกันครั้งแรกออกไปก่อนและประหยัดคุณจากการประมวลผลไฟล์ทั้งหมด

มันจะยังคงใช้เวลาเต็มสำหรับไฟล์ที่เหมือนกัน


2
ฉันชอบความคิด แต่จะไม่ทำงานในสถานการณ์ของฉันเพราะฉันจะจบลงด้วยไฟล์ที่ไม่เปลี่ยนแปลงจำนวนมากตลอดเวลา
2552

1
คุณตรวจสอบไฟล์ทุก ๆ 100mb อย่างไร
สมิ ธ

1
ไม่ใช่ความคิดที่ดีเมื่อใช้การตรวจสอบด้วยเหตุผลด้านความปลอดภัยเนื่องจากผู้โจมตีสามารถเปลี่ยนไบต์ที่คุณยกเว้นได้
b.kiener

2
+1 นี่เป็นแนวคิดที่ยอดเยี่ยมเมื่อคุณทำการเปรียบเทียบแบบหนึ่งต่อหนึ่ง น่าเสียดายที่ฉันกำลังใช้แฮช MD5 เป็นดัชนีในการค้นหาไฟล์ที่ไม่ซ้ำกันในกลุ่มรายการที่ซ้ำกันหลายรายการ (การตรวจสอบหลายต่อหลายครั้ง)
Nathan Goings

1
@ b.kiener ไม่มีการแยกไบต์ คุณเข้าใจเขาผิด
Soroush Falahati

47

ตามที่ Anton Gogolev ตั้งข้อสังเกต FileStream อ่าน 4096 ไบต์ในแต่ละครั้งโดยค่าเริ่มต้น แต่คุณสามารถระบุค่าอื่น ๆ โดยใช้ตัวสร้าง FileStream:

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

โปรดทราบว่า Brad Abrams จาก Microsoft เขียนในปี 2004:

ไม่มีประโยชน์จากการตัด BufferedStream รอบ ๆ FileStream เราคัดลอกตรรกะการบัฟเฟอร์ของ BufferedStream ไปที่ FileStream เมื่อประมาณ 4 ปีที่แล้วเพื่อส่งเสริมประสิทธิภาพการเริ่มต้นที่ดีขึ้น

แหล่ง


22

เรียกหน้าต่างท่าเรือmd5sum.exe เร็วกว่าการใช้. NET ประมาณสองเท่า (อย่างน้อยในเครื่องของฉันที่ใช้ไฟล์ 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 - การใช้ md5sums.exe จาก pc-tools.net/win32/md5sums ทำให้มันเร็วจริงๆ 1681457152 ไบต์, 8672 ms = 184.91 MB / วินาที -> 1,6GB ~ 9 วินาทีนี่จะเร็วพอสำหรับจุดประสงค์ของฉัน
crono

16

โอเค - ขอบคุณพวกคุณทุกคน - ขอผมสรุป:

  1. ใช้ exe "ดั้งเดิม"เพื่อทำ hashing ใช้เวลาตั้งแต่ 6 นาทีถึง 10 วินาทีซึ่งมีขนาดใหญ่มาก
  2. การเพิ่มบัฟเฟอร์เร็วยิ่งขึ้น - ไฟล์ 1.6GB ใช้เวลา 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½ GB ผลที่ได้คือ

  • 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, ram 12 GB และไดรฟ์ OCZ Vertex 4 256 GB SSD

ดังนั้นฉันจึงคิดว่าฮาร์ดไดรฟ์ 2TB มาตรฐานเกี่ยวกับอะไร และผลลัพธ์ก็เป็นเช่นนี้

  • 10.000: 368,52 วินาที
  • 100,000: 364,15 วินาที
  • 1.000.000: 363,06 วินาที
  • 10.000.000: 678,96s
  • 100.000.000: 617,89s
  • 1.000.000.000: 626,86s
  • และสำหรับไม่มีบัฟเฟอร์ 368,24

ดังนั้นฉันจะแนะนำไม่บัฟเฟอร์หรือบัฟเฟอร์สูงสุด 1 mill


ฉันไม่เข้าใจ การทดสอบนี้ขัดแย้งกับคำตอบที่ยอมรับจาก Anton Gogolev อย่างไร
buddybubble

คุณสามารถเพิ่มคำอธิบายของแต่ละฟิลด์ในข้อมูลของคุณได้หรือไม่?
videoguy

2

คุณกำลังทำอะไรผิดพลาด (อาจเป็นบัฟเฟอร์การอ่านที่เล็กเกินไป) ในเครื่องที่มีอายุไม่เกินเกณฑ์ (Athlon 2x1800MP จากปี 2002) ที่มี DMA บนดิสก์อาจจะไม่ตี (6.6M / s ช้ามากเมื่อทำการอ่านตามลำดับ):

สร้างไฟล์ 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 อย่างสม่ำเสมอสำหรับฉัน (รันหลายครั้ง)


ใช่ - ฉันจะพยายามเพิ่มบัฟเฟอร์ - เหมือน Anton Gogolev ที่เต็มไปด้วย ฉันวิ่งผ่าน MD5.exe "ดั้งเดิม" ซึ่งใช้เวลา 9 วินาทีกับไฟล์ 1,6 GB
crono

2

ฉันรู้ว่าฉันมาสาย แต่ได้ทำการทดสอบก่อนที่จะใช้โซลูชัน

ฉันไม่ดำเนินการทดสอบกับ inbuilt ระดับ MD5 และmd5sum.exe ในกรณีของฉันคลาส inbuilt ใช้เวลา 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);
        }
    }

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.