创建文件校验和时出现性能问题
Performance issues while creating file checksums
我正在编写一个控制台应用程序,它遍历二叉树并根据它们的 md5 校验和搜索新的或更改的文件。
整个过程是可以接受的快速(约 70.000 个文件需要 14 秒)但是生成校验和需要大约 5 分钟,这太慢了......
有什么改进这个过程的建议吗?我的哈希函数如下:
private string getMD5(string filename)
{
using (var md5 = new MD5CryptoServiceProvider())
{
if (File.Exists(@filename))
{
try
{
var buffer = md5.ComputeHash(File.ReadAllBytes(filename));
var sb = new StringBuilder();
for (var i = 0; i < buffer.Length; i++)
{
sb.Append(buffer[i].ToString("x2"));
}
return sb.ToString();
}
catch (Exception)
{
Program.logger.log("Error while creating checksum!", Program.logger.LOG_ERROR);
return "";
}
}
else
{
return "";
}
}
}
为了创建哈希,您必须读取文件的每个最后一个字节。所以这个操作是磁盘限制的,而不是 CPU 限制的,并且与文件的大小成比例地缩放。多线程将无济于事。
除非 FS 可以以某种方式为您计算和存储哈希,否则没有办法加快速度。您依赖于 FS 为您所做的跟踪更改。
通常检查 "changed files" 的程序(如备份例程)不会 正是出于这个原因计算哈希值。他们可能仍会出于验证目的计算并存储它,但仅此而已。
除非用户进行一些严重的(NTFS 驱动程序加载级别)破坏,"last changed" 日期和文件大小足以检测到更改。也许还检查存档位,但现在很少使用了。
针对此类场景(列出文件并处理它们)的一个小改进是使用“Enumerate Files”而不是列出文件。但是在 14 秒 Listing/5 分钟的处理过程中不会产生任何相关效果。
好吧,接受的答案是无效的,因为当然有一种方法可以提高您的代码性能。然而,它对其他一些想法有效)
除了磁盘I/O,这里的主要障碍是内存分配。这里有一些应该提高速度的想法:
- 不要读取内存中的整个文件进行计算,它很慢,并且会通过LOH对象产生很大的内存压力。而是将文件作为流打开,并按块计算哈希值。
- 使用
ComputeHash
流覆盖时速度变慢的原因,因为它在内部使用非常小的缓冲区(4kb),所以选择合适的缓冲区大小(256kb或更大,通过试验找到最佳值)
- 使用TransformBlock and TransformFinalBlock函数计算哈希值。您可以为
outputBuffer
参数传递 null。
- 重复使用该缓冲区进行后续文件哈希计算,因此无需额外分配。
- 此外,您可以重复使用
MD5CryptoServiceProvider
,但好处值得怀疑。
- 最后,您可以应用异步模式从流中读取块,因此当您为前一个块计算部分哈希时,OS 将同时从磁盘读取下一个块。当然这样的代码比较难写,至少需要两个buffer(也可以复用),但是对速度的影响很大。
- 作为一个小改进,不检查文件是否存在。我相信,您的函数是从某些枚举中调用的,并且该文件同时被删除的可能性很小。
以上所有内容都适用于大中型文件。相反,如果您有很多非常小的文件,则可以通过并行处理文件来加快计算速度。实际上,并行化也可以帮助处理大文件,但这有待衡量。
最后,如果碰撞对你没有太大的影响,你可以选择成本较低的散列算法,例如 CRC。
我正在编写一个控制台应用程序,它遍历二叉树并根据它们的 md5 校验和搜索新的或更改的文件。 整个过程是可以接受的快速(约 70.000 个文件需要 14 秒)但是生成校验和需要大约 5 分钟,这太慢了......
有什么改进这个过程的建议吗?我的哈希函数如下:
private string getMD5(string filename)
{
using (var md5 = new MD5CryptoServiceProvider())
{
if (File.Exists(@filename))
{
try
{
var buffer = md5.ComputeHash(File.ReadAllBytes(filename));
var sb = new StringBuilder();
for (var i = 0; i < buffer.Length; i++)
{
sb.Append(buffer[i].ToString("x2"));
}
return sb.ToString();
}
catch (Exception)
{
Program.logger.log("Error while creating checksum!", Program.logger.LOG_ERROR);
return "";
}
}
else
{
return "";
}
}
}
为了创建哈希,您必须读取文件的每个最后一个字节。所以这个操作是磁盘限制的,而不是 CPU 限制的,并且与文件的大小成比例地缩放。多线程将无济于事。
除非 FS 可以以某种方式为您计算和存储哈希,否则没有办法加快速度。您依赖于 FS 为您所做的跟踪更改。
通常检查 "changed files" 的程序(如备份例程)不会 正是出于这个原因计算哈希值。他们可能仍会出于验证目的计算并存储它,但仅此而已。
除非用户进行一些严重的(NTFS 驱动程序加载级别)破坏,"last changed" 日期和文件大小足以检测到更改。也许还检查存档位,但现在很少使用了。
针对此类场景(列出文件并处理它们)的一个小改进是使用“Enumerate Files”而不是列出文件。但是在 14 秒 Listing/5 分钟的处理过程中不会产生任何相关效果。
好吧,接受的答案是无效的,因为当然有一种方法可以提高您的代码性能。然而,它对其他一些想法有效)
除了磁盘I/O,这里的主要障碍是内存分配。这里有一些应该提高速度的想法:
- 不要读取内存中的整个文件进行计算,它很慢,并且会通过LOH对象产生很大的内存压力。而是将文件作为流打开,并按块计算哈希值。
- 使用
ComputeHash
流覆盖时速度变慢的原因,因为它在内部使用非常小的缓冲区(4kb),所以选择合适的缓冲区大小(256kb或更大,通过试验找到最佳值) - 使用TransformBlock and TransformFinalBlock函数计算哈希值。您可以为
outputBuffer
参数传递 null。 - 重复使用该缓冲区进行后续文件哈希计算,因此无需额外分配。
- 此外,您可以重复使用
MD5CryptoServiceProvider
,但好处值得怀疑。 - 最后,您可以应用异步模式从流中读取块,因此当您为前一个块计算部分哈希时,OS 将同时从磁盘读取下一个块。当然这样的代码比较难写,至少需要两个buffer(也可以复用),但是对速度的影响很大。
- 作为一个小改进,不检查文件是否存在。我相信,您的函数是从某些枚举中调用的,并且该文件同时被删除的可能性很小。
以上所有内容都适用于大中型文件。相反,如果您有很多非常小的文件,则可以通过并行处理文件来加快计算速度。实际上,并行化也可以帮助处理大文件,但这有待衡量。
最后,如果碰撞对你没有太大的影响,你可以选择成本较低的散列算法,例如 CRC。