web-dev-qa-db-ja.com

C#で大きなファイルのチェックサムを作成する最速の方法は何ですか

一部のマシン間で大きなファイルを同期する必要があります。ファイルのサイズは最大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);
    }
}

問題はランタイムでした:
-1,6 GBファイルのSHA256で-> 20分
-MD5、1,6 GBファイル-> 6.15分

より良い-より速い-チェックサムを取得する方法はありますか(おそらくより良いハッシュ関数で)?

124
crono

ここでの問題は、SHA256Managedが一度に4096バイトを読み取る(FileStreamから継承し、Read(byte[], int, int)をオーバーライドしてファイルストリームから読み取る量を確認する)ことです。 IO。

速度を上げるには(SHA256を使用してマシン上で2 Gbファイルをハッシュするのに2分、MD5に1分)、FileStreamBufferedStreamにラップし、適切なサイズのバッファーサイズを設定します(〜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
}
110
Anton Gogolev

ファイル全体をチェックサムするのではなく、100MBごとにチェックサムを作成するので、各ファイルにはチェックサムのコレクションがあります。

次に、チェックサムを比較するときに、最初の異なるチェックサムの後で比較を停止し、早めに出て、ファイル全体の処理を省くことができます。

同一のファイルの場合は、まだフルタイムがかかります。

60
Binary Worrier

Anton Gogolevが指摘したように、FileStreamはデフォルトで一度に4096バイトを読み取りますが、FileStreamコンストラクターを使用して他の値を指定できます。

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

MicrosoftのBrad Abramsは2004年に次のように書いていることに注意してください。

fileedStreamをBufferedStreamでラップしてもメリットはありません。 4年ほど前にBufferedStreamのバッファリングロジックをFileStreamにコピーして、デフォルトのパフォーマンスを向上させました

ソース

42
Tal Aloni

md5sum.exe のWindowsポートを呼び出します。 .NET実装の約2倍の速度です(少なくとも私のマシンでは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 ();
}
22
Christian Birkl

わかりました-皆さんに感謝します-これをまとめましょう:

  1. "ネイティブ" exeを使用 ハッシュを行うには、6分から10秒まで時間がかかりました。
  2. バッファを増やす はさらに高速でした-1.6GBファイルは.NetのMD5を使用して5.2秒かかったので、このソリューションを使用します-再度ありがとう
15
crono

このコードを実行して、バッファーサイズでテストを行いました

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,15s
  • 100.000.000:435,15s
  • 1.000.000.000:434,31s
  • 元のバッファリングされていないコードを使用した場合、376,22秒。

I5 2500K CPU、12 GB RAM、OCZ Vertex 4 256 GB SSDドライブを実行しています。

だから、標準の2TBハードドライブはどうだろうと思った。結果はこんな感じでした

  • 10.000:368,52秒
  • 100.000:364,15秒
  • 1.000.000:363,06s
  • 10.000.000:678,96s
  • 100.000.000:617,89s
  • 1.000.000.000:626,86s
  • バッファなしの場合は368,24

したがって、バッファーなしまたは最大1ミルのバッファーをお勧めします。

10
Anders

何か間違ったことをしている(おそらく読み取りバッファが小さすぎる)。ディスクのDMAがおそらく暴走していない(2002年からのAthlon 2x1800MP)まともな時代のマシンでは(連続読み取りを行うと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より遅いです(数回繰り返します)。

2
Pasi Savolainen

私はパーティーに遅れていることを知っていますが、実際にソリューションを実装する前にテストを実行しました。

組み込みの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);
        }
    }
0