【问题标题】:What is the fastest way to create a checksum for large files in C#在 C# 中为大文件创建校验和的最快方法是什么
【发布时间】:2010-11-13 17:43:24
【问题描述】:

我必须在一些机器上同步大文件。文件最大可达 6GB。每隔几周手动进行一次同步。我不能考虑文件名,因为它们可以随时更改。

我的计划是在目标 PC 和源 PC 上创建校验和,然后将所有尚未在目标中的带有校验和的文件复制到目标。 我的第一次尝试是这样的:

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 分钟

是否有更好更快的方法来获取校验和(可能使用更好的哈希函数)?

【问题讨论】:

  • 您真的需要检查校验和吗?你是如何复制文件的?如果你在 Windows 上,我会使用最新版本的 Robocopy ...
  • 很好的提示,如果两个候选文件之间的文件大小不同stackoverflow.com/a/288756/74585

标签: c# .net large-files checksum


【解决方案1】:

这里的问题是 SHA256Managed 一次读取 4096 个字节(从 FileStream 继承并覆盖 Read(byte[], int, int) 以查看它从文件流中读取了多少),这对于磁盘 IO 来说缓冲区太小了。

为了加快速度(在我的机器上使用 SHA256 散列 2 Gb 文件需要 2 分钟,MD5 需要 1 分钟)将 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
}

【讨论】:

  • OK - 这产生了差异 - 在我的机器上使用 MD5 散列 1.6GB 文件需要 5.2 秒(QuadCode @2.6 GHz,8GB Ram) - 甚至比原生实现更快...
  • 我不明白。我刚刚尝试了这个建议,但差异很小甚至没有。 1024mb 文件没有缓冲 12-14 秒,缓冲也有 12-14 秒 - 我知道读取数百个 4k 块会产生更多的 IO,但我问自己框架或框架下面的本机 API 是否已经处理这个问题..
  • 派对有点晚了,但对于 FileStreams,不再需要将流包装在 BufferedStream 中,因为它现在已经在 FileStream 本身中完成了。 Source
  • 我只是用较小的文件(
  • 我使用了 BufferedStream /w 512 kB 而不是 1024 kB。 1.8 GB 文件在 30 秒内解决。
【解决方案2】:

不要对整个文件进行校验和,每 100mb 左右创建校验和,因此每个文件都有一个校验和集合。

然后在比较校验和时,您可以在第一个不同的校验和之后停止比较,尽早退出,并让您免于处理整个文件。

相同的文件仍然需要全部时间。

【讨论】:

  • 我喜欢这个想法,但它不适用于我的场景,因为随着时间的推移,我最终会得到很多未更改的文件。
  • 如何对文件的每 100mb 进行校验和?
  • 出于安全原因使用校验和不是一个好主意,因为攻击者可以更改您已排除的字节。
  • +1 当您执行一对一比较时,这是一个绝妙的主意。不幸的是,我使用 MD5 哈希作为索引来查找许多重复文件中的唯一文件(多对多检查)。
  • @b.kiener 不排除任何字节。你误会了他。
【解决方案3】:

As Anton Gogolev noted,FileStream默认一次读取4096字节, 但是您可以使用 FileStream 构造函数指定任何其他值:

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

请注意,微软的 Brad Abrams 在 2004 年写道:

将 BufferedStream 包裹在 文件流。我们将 BufferedStream 的缓冲逻辑复制到 大约 4 年前的 FileStream 以鼓励更好的默认性能

source

【讨论】:

    【解决方案4】:

    调用md5sum.exe的windows端口。它大约是 .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 ();
    }
    

    【讨论】:

    • WOW - 使用 pc-tools.net/win32/md5sums 中的 md5sums.exe 使其非常快。 1681457152 字节,8672 毫秒 = 184.91 MB/秒 -> 1,6GB ~ 9 秒这对于我的目的来说已经足够快了。
    【解决方案5】:

    好的 - 谢谢大家 - 让我总结一下:

    1. using a "native" exe 做 散列需要时间从 6 分钟到 10 秒,这是巨大的。
    2. Increasing the buffer 甚至更快 - 1.6GB 文件在 .Net 中使用 MD5 需要 5.2 秒,所以我会采用这个解决方案 - 再次感谢

    【讨论】:

      【解决方案6】:

      我用缓冲区大小做了测试,运行这段代码

      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.24s
      • 100.000: 362.55s
      • 1.000.000: 361,53s
      • 10.000.000: 434,15s
      • 100.000.000: 435,15s
      • 1.000.000.000: 434,31s
      • 使用原始无缓冲代码时为 376,22 秒。

      我正在运行 i5 2500K CPU、12 GB 内存和 OCZ Vertex 4 256 GB SSD 驱动器。

      所以我想,标准的 2TB 硬盘怎么样。结果是这样的

      • 10.000: 368.52s
      • 100.000: 364,15s
      • 1.000.000: 363,06s
      • 10.000.000: 678,96s
      • 100.000.000: 617,89s
      • 1.000.000.000: 626,86s
      • 对于无缓冲 368,24

      所以我建议要么不使用缓冲液,要么使用最大 1 磨机的缓冲液。

      【讨论】:

      • 我不明白。这个测试如何与 Anton Gogolev 接受的答案相矛盾?
      • 你能在你的数据中添加每个字段的描述吗?
      【解决方案7】:

      我知道我迟到了,但在实际实施解决方案之前进行了测试。

      我确实对内置的 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);
              }
          }
      

      【讨论】:

        【解决方案8】:

        你可以看看 XxHash.Net (https://github.com/wilhelmliao/xxHash.NET)
        xxHash 算法似乎比其他算法更快。
        xxHash 网站上的一些基准测试:https://github.com/Cyan4973/xxHash

        PS:我还没用过。

        【讨论】:

          【解决方案9】:

          您做错了什么(可能是读取缓冲区太小)。在一台过时的机器上(2002 年的 Athlon 2x1800MP)磁盘上的 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。
          猜你喜欢
          • 2010-12-04
          • 2016-06-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-03-24
          • 1970-01-01
          • 2011-05-11
          • 2010-10-03
          相关资源
          最近更新 更多