Один из способов ускорить это - использовать двойную буферизацию, чтобы один поток мог читать из файла в один буфер, в то время как MD5 рассчитывается для другого буфера.
Это позволяет перекрывать друг друга I / O с вычислением.
Лучший способ сделать это - иметь одну задачу, которая отвечала бы за вычисление Md5 для всех блоков данных, но, поскольку это немного усложняет код (и вряд ли даст гораздо лучшие результаты) Вместо этого я создам новую задачу для каждого блока.
Код выглядит следующим образом:
public static async Task<byte[]> ComputeMd5Async(string filename)
{
using (var md5 = MD5.Create())
using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 16384, FileOptions.SequentialScan | FileOptions.Asynchronous))
{
const int BUFFER_SIZE = 16 * 1024 * 1024; // Adjust buffer size to taste.
byte[] buffer1 = new byte[BUFFER_SIZE];
byte[] buffer2 = new byte[BUFFER_SIZE];
byte[] buffer = buffer1; // Double-buffered, so use 'buffer' to switch between buffers.
var task = Task.CompletedTask;
while (true)
{
buffer = (buffer == buffer1) ? buffer2 : buffer1; // Swap buffers for double-buffering.
int n = await file.ReadAsync(buffer, 0, buffer.Length);
await task;
task.Dispose();
if (n == 0)
break;
var block = buffer;
task = Task.Run(() => md5.TransformBlock(block, 0, n, null, 0));
}
md5.TransformFinalBlock(buffer, 0, 0);
return md5.Hash;
}
}
Вот скомпилированное тестовое приложение:
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace Demo
{
class Program
{
static async Task Main()
{
string file = @"C:\ISO\063-2495-00-Rev 1.iso";
Stopwatch sw = new Stopwatch();
for (int i = 0; i < 4; ++i) // Try several times.
{
sw.Restart();
var hash = await ComputeMd5Async(file);
Console.WriteLine("ComputeMd5Async() Took " + sw.Elapsed);
Console.WriteLine(string.Join(", ", hash));
Console.WriteLine();
sw.Restart();
hash = ComputeMd5(file);
Console.WriteLine("ComputeMd5() Took " + sw.Elapsed);
Console.WriteLine(string.Join(", ", hash));
Console.WriteLine();
}
}
public static byte[] ComputeMd5(string filename)
{
using var md5 = MD5.Create();
using var stream = File.OpenRead(filename);
md5.ComputeHash(stream);
return md5.Hash;
}
public static async Task<byte[]> ComputeMd5Async(string filename)
{
using (var md5 = MD5.Create())
using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 16384, FileOptions.SequentialScan | FileOptions.Asynchronous))
{
const int BUFFER_SIZE = 16 * 1024 * 1024; // Adjust buffer size to taste.
byte[] buffer1 = new byte[BUFFER_SIZE];
byte[] buffer2 = new byte[BUFFER_SIZE];
byte[] buffer = buffer1; // Double-buffered, so use 'buffer' to switch between buffers.
var task = Task.CompletedTask;
while (true)
{
buffer = (buffer == buffer1) ? buffer2 : buffer1; // Swap buffers for double-buffering.
int n = await file.ReadAsync(buffer, 0, buffer.Length);
await task;
task.Dispose();
if (n == 0)
break;
var block = buffer;
task = Task.Run(() => md5.TransformBlock(block, 0, n, null, 0));
}
md5.TransformFinalBlock(buffer, 0, 0);
return md5.Hash;
}
}
}
}
И результаты, которые я получил для файла размером ~ 2,5 ГБ:
ComputeMd5Async() Took 00:00:04.8066365
49, 54, 154, 19, 115, 198, 28, 163, 5, 182, 183, 91, 2, 5, 241, 253
ComputeMd5() Took 00:00:06.9654982
49, 54, 154, 19, 115, 198, 28, 163, 5, 182, 183, 91, 2, 5, 241, 253
ComputeMd5Async() Took 00:00:04.7018911
49, 54, 154, 19, 115, 198, 28, 163, 5, 182, 183, 91, 2, 5, 241, 253
ComputeMd5() Took 00:00:07.3552470
49, 54, 154, 19, 115, 198, 28, 163, 5, 182, 183, 91, 2, 5, 241, 253
ComputeMd5Async() Took 00:00:04.6536709
49, 54, 154, 19, 115, 198, 28, 163, 5, 182, 183, 91, 2, 5, 241, 253
ComputeMd5() Took 00:00:06.9807878
49, 54, 154, 19, 115, 198, 28, 163, 5, 182, 183, 91, 2, 5, 241, 253
ComputeMd5Async() Took 00:00:04.7271215
49, 54, 154, 19, 115, 198, 28, 163, 5, 182, 183, 91, 2, 5, 241, 253
ComputeMd5() Took 00:00:07.4089941
49, 54, 154, 19, 115, 198, 28, 163, 5, 182, 183, 91, 2, 5, 241, 253
Так что версия asyn c с двойной буферизацией работает примерно на 50% быстрее.
Могут быть более быстрые способы, но это довольно простой подход.