直列処理
using System.Security.Cryptography;
using System.Text;
static string CalcMd5(string path)
{
using var md5 = MD5.Create();
using var stream = File.OpenRead(path);
var hash = md5.ComputeHash(stream);
return Convert.ToHexString(hash); // 大文字16進
}
while (true)
{
var line = Console.ReadLine();
if (line == null) break;
var path = line.Trim();
if (path.Length == 0) continue;
try
{
var hash = CalcMd5(path);
Console.WriteLine($"{path},{hash}");
}
catch (Exception ex)
{
Console.Error.WriteLine($"{path},ERROR,{ex.Message}");
}
}
// 使い方
// Measure-Command { ls -path "G:\" -Recurse -Filter "*.avi" | % { $_.FullName } | .\MD5Single.exe > output.txt }
// Days : 0
// Hours : 0
// Minutes : 2
// Seconds : 46
// Milliseconds : 205
// Ticks : 1662052268
// TotalDays : 0.00192367160648148
// TotalHours : 0.0461681185555556
// TotalMinutes : 2.77008711333333
// TotalSeconds : 166.2052268
// TotalMilliseconds : 166205.2268
パイプライン処理
using System.Security.Cryptography;
using System.Threading.Channels;
var pathChannel = Channel.CreateUnbounded<string>();
var dataChannel = Channel.CreateUnbounded<(string path, byte[] data)>();
var hashChannel = Channel.CreateUnbounded<(string path, string hash)>();
// --------------------
// Stage 1: stdin → path
// --------------------
var readerTask = Task.Run(async () =>
{
while (true)
{
var line = Console.ReadLine();
if (line == null) break;
var path = line.Trim();
if (path.Length == 0) continue;
await pathChannel.Writer.WriteAsync(path);
}
pathChannel.Writer.Complete();
});
// --------------------
// Stage 2: path → file read
// --------------------
var fileReadTask = Task.Run(async () =>
{
await foreach (var path in pathChannel.Reader.ReadAllAsync())
{
try
{
var data = await File.ReadAllBytesAsync(path);
await dataChannel.Writer.WriteAsync((path, data));
}
catch (Exception ex)
{
await hashChannel.Writer.WriteAsync((path, $"ERROR,{ex.Message}"));
}
}
dataChannel.Writer.Complete();
});
// --------------------
// Stage 3: data → MD5
// --------------------
var hashTask = Task.Run(async () =>
{
using var md5 = MD5.Create();
await foreach (var (path, data) in dataChannel.Reader.ReadAllAsync())
{
try
{
var hash = Convert.ToHexString(md5.ComputeHash(data));
await hashChannel.Writer.WriteAsync((path, hash));
}
catch (Exception ex)
{
await hashChannel.Writer.WriteAsync((path, $"ERROR,{ex.Message}"));
}
}
hashChannel.Writer.Complete();
});
// --------------------
// Stage 4: stdout
// --------------------
var writerTask = Task.Run(async () =>
{
await foreach (var (path, hash) in hashChannel.Reader.ReadAllAsync())
{
Console.WriteLine($"{path},{hash}");
}
});
await Task.WhenAll(readerTask, fileReadTask, hashTask, writerTask);
/*
使い方
Measure-Command { ls -path "G:\" -Recurse -Filter "*.avi" | % { $_.FullName } | .\MD5Pipeline.exe > output.txt }
Days : 0
Hours : 0
Minutes : 1
Seconds : 53
Milliseconds : 914
Ticks : 1139147432
TotalDays : 0.00131845767592593
TotalHours : 0.0316429842222222
TotalMinutes : 1.89857905333333
TotalSeconds : 113.9147432
TotalMilliseconds : 113914.7432
*/
並列処理
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
static string CalcMd5(string path)
{
using var md5 = MD5.Create();
using var stream = File.OpenRead(path);
var hash = md5.ComputeHash(stream);
return Convert.ToHexString(hash); // 大文字16進
}
var paths = new List<string>();
while (true)
{
var line = Console.ReadLine();
if (line == null) break;
var path = line.Trim();
if (path.Length > 0)
paths.Add(path);
}
var results = new ConcurrentQueue<string>();
Parallel.ForEach(paths, path =>
{
try
{
var hash = CalcMd5(path);
results.Enqueue($"{path},{hash}");
}
catch (Exception ex)
{
results.Enqueue($"{path},ERROR,{ex.Message}");
}
});
foreach (var line in results)
{
Console.WriteLine(line);
}
/*
実行例
Measure-Command { ls -path "G:\" -Recurse -Filter "*.avi" | % { $_.FullName } | .\MD5Parallel.exe > output.txt }
Days : 0
Hours : 0
Minutes : 0
Seconds : 51
Milliseconds : 863
Ticks : 518630308
TotalDays : 0.000600266560185185
TotalHours : 0.0144063974444444
TotalMinutes : 0.864383846666667
TotalSeconds : 51.8630308
TotalMilliseconds : 51863.0308
*/
速度比較
| 方式 |
実行時間(分:秒) |
総ミリ秒 |
特徴・備考 |
| 直列処理 |
2:46 |
166,205 ms |
完全逐次処理。I/O待ちとCPU待ちが重なり遅い |
| パイプライン処理 |
1:53 |
113,914 ms |
I/OとMD5計算を分離し同時進行。 |
| 並列処理 |
0:51 |
51,863 ms |
Parallel.ForEachによる並列処理。 |
| 方式 |
出力順序 |
理由 |
| 直列処理 |
保証される |
1件ずつ順番に処理・出力しているため |
| パイプライン処理 |
保証される |
各データがパイプライン上を順序通り流れるため |
| 並列処理 |
保証されない |
処理完了順に結果が出力されるため |
コメント