最*需要在计算大文件的 MD5 值时显示进度,于是我写了如下的代码:

public long Length {get; private set; }

public long Position { get; private set; }

public async Task ComputeMD5Async(string file, CancellationToken cancellationToken)
{
using var fs = File.OpenRead(file);
Length = fs.Length;
var task = MD5.HashDataAsync(fs, cancellationToken);
var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(10));
while (await timer.WaitForNextTickAsync(cancellationToken))
{
Position = fs.Position;
if (task.IsCompleted)
{
break;
}
}
}

运行的时候发现不对劲儿了,我的校验速度只能跑到 350MB/s,而别人的却能跑到 500MB/s,相同的设备怎么差距有这么大?带这个疑问我去看了看别人的源码,发现是这么写的:

protected long _progressPerFileSizeCurrent;

protected byte[] CheckHash(Stream stream, HashAlgorithm hashProvider, CancellationToken token)
{
byte[] buffer = new byte[1 << 20];
int read;
while ((read = stream.Read(buffer)) > 0)
{
token.ThrowIfCancellationRequested();
hashProvider.TransformBlock(buffer, 0, read, buffer, 0);
_progressPerFileSizeCurrent += read;
}
hashProvider.TransformFinalBlock(buffer, 0, read);
return hashProvider.Hash;
}

这里使用了 HashAlgorithm.TransformBlock 方法,它能计算输入字节数组指定区域的哈希值,并将中间结果暂时存储起来,最后再调用 HashAlgorithm.TransformFinalBlock 结束计算。上述代码中缓冲区 buffer 大小是 1MB,我敏锐地察觉到 MD5 计算速度可能与这个值有关,接着我又去翻了翻 MD5.HashDataAsync 的源码。

// System.Security.Cryptography.LiteHashProvider
private static async ValueTask<int> ProcessStreamAsync<T>(T hash, Stream source, Memory<byte> destination, CancellationToken cancellationToken) where T : ILiteHash
{
using (hash)
{
byte[] rented = CryptoPool.Rent(4096); int maxRead = 0;
int read; try
{
while ((read = await source.ReadAsync(rented, cancellationToken).ConfigureAwait(false)) > 0)
{
maxRead = Math.Max(maxRead, read);
hash.Append(rented.AsSpan(0, read));
} return hash.Finalize(destination.Span);
}
finally
{
CryptoPool.Return(rented, clearSize: maxRead);
}
}
}

源码中最关键的是上面这部分,缓冲区 rented 设置为 4KB,与 1MB 相差甚远,原因有可能就在这里。

为了找到最佳的缓冲区值,我跑了一大堆 BenchMark,覆盖了从 32B 到 64MB 的范围。没什么技术含量,但工作量实在不小。测试使用 1GB 的文件,基准测试是对 1GB 大小的数组直接调用 MD5.HashData,实际的测试代码如下,分别使用内存流 MemoryStream 和文件流 FileStream 作为入参 Stream,对比无硬盘 IO 和实际读取文件的速度。

public async Task HashDataAsync(Stream stream)
{
var hash = MD5.Create();
byte[] buffer = new byte[1 << size];
int read = 0;
while ((read = await stream.ReadAsync(buffer)) != 0)
{
hash.TransformBlock(buffer, 0, read, buffer, 0);
}
hash.TransformFinalBlock(buffer, 0, read);
if (!(hash.Hash?.SequenceEqual(fileHash) ?? false))
{
throw new Exception("Compute error");
}
}

基准测试是那条红色虚线,是所有测试结果中最快的。橙色的曲线是 MemoryStream 的测试结果,在缓存块的 2KB 处降到了一个较低的位置,后续耗时无明显下降。这证明 .NET 源码中使用 4KB 大小的块是一个合理的选择,但是它没有考虑文件 IO 的延迟影响。蓝色的曲线是最接*显示的测试结果,缓存块大于 32KB 时的测试结果才接*于*稳。

总结一下,MD5.HashDataAsync 过慢的原因是文件 IO 影响到了计算速度。使用文件流进行 MD5 校验的时候,缓冲区至少需要 64KB,总体速度才不会被文件 IO 拖后腿。

不同大小的缓冲区对 MD5 计算速度的影响的更多相关文章

  1. Java中比较不同的MD5计算方式

    在项目中经常需要使用计算文件的md5,用作一些用途,md5计算算法,通常在网络上查询时,一般给的算法是读取整个文件的字节流,然后计算文件的md5,这种方式当文件较大,且有很大并发量时,则可能导致内存打 ...

  2. iOS BCD码、数据流、字节和MD5计算

    一.各个之间的相互转换 1.字符串转数据流NSData NSString *str = @"abc123"; NSData *dd = [str dataUsingEncoding ...

  3. php UTF8 转字节数组,后使用 MD5 计算摘要

    Hex.encodeHexString(md5.digest);按 UTF8 转字节数组,后使用 MD5 计算摘要,得到 16 字节数组,使用 Hex 转为长度为 32 的字符串,保持小写 bin2h ...

  4. 大文本 通过 hadoop spark map reduce 获取 特征列 的 属性值 计算速度

    大文本 通过 hadoop spark map reduce   获取 特征列  的 属性值  计算速度

  5. 一行代码加快pandas计算速度

    一行代码加快pandas计算速度 DASK https://blog.csdn.net/sinat_38682860/article/details/84844964 https://cloud.te ...

  6. Tensorflow使用训练好的模型进行测试,发现计算速度越来越慢

    实验时要对多个NN模型进行对比,依次加载直到第8个模型时,发现运行速度明显变慢而且电脑开始卡顿,查看内存占用90+%. 原因:使用过的NN模型还会保存在内存,继续加载一方面使新模型加载特别特别慢,另一 ...

  7. Cocos Creator 热更新文件MD5计算和需要注意的问题

    Creator的热更新使用jsb.热更新基本按照 http://docs.cocos.com/creator/manual/zh/advanced-topics/hot-update.html?h=% ...

  8. (一)tensorflow-gpu2.0学习笔记之开篇(cpu和gpu计算速度比较)

    摘要: 1.以动态图形式计算一个简单的加法 2.cpu和gpu计算力比较(包括如何指定cpu和gpu) 3.关于gpu版本的tensorflow安装问题,可以参考另一篇博文:https://www.c ...

  9. hashlib的md5计算

    hashlib的md5计算 hashlib概述 涉及加密服务:Cryptographic Services 其中 hashlib是涉及 安全散列 和 消息摘要 ,提供多个不同的加密算法借口,如SHA1 ...

  10. .NET 的 Debug 和 Release build 对执行速度的影响

    这篇文章发布于我的 github 博客:原文 在真正开始讨论之前先定义一下 Scope. 本文讨论的范围限于执行速度,内存占用什么的不在评估的范围之内. 本文不讨论算法:编译器带来的优化基本上属于底层 ...

随机推荐

  1. 系统评价——数据包络分析DEA的R语言实现(七)

    数据包络分析(Data envelopment analysis,DEA)是运筹学中用于测量决策部门生产效率的一种方法,它是基于相对效率发展的崭新的效率评估方法. 详细来说,通过使用数学规划模型,计算 ...

  2. 修复kube-proxy证书权限过大问题

    修复kube-proxy证书权限过大问题 之前kube-proxy服务都是用admin集群证书,造成权限过大不安全,后续该问题,将在文档中修复 请关注 https://github.com/cby-c ...

  3. LeeCode数组问题:二分查找

    LeeCode 704 二分查找 题目描述: 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标, ...

  4. RDIFramework.NET开发框架WinForm版新增编码管理

    1.概述 编码管理是企业管理和信息化建设过程中一个至关重要的环节.各信息化系统是通过编码来标识业务对象的,统一的编码方案可以作为各系统的通话语言,也可以为信息化系统的集成提供便利.建设数据编码的基本原 ...

  5. 探索FSM (有限状态机)应用

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值.. 本文作者:木杪 有限状态机(FSM) 是计算机科学中的一种数学模型 ...

  6. 在 plist 中轻松转换两种格式

      在 plist 中,有两种常见的格式:.plist 和 .plistx.这两种格式有什么区别?它们之间如何相互转换?本文将为您详细解答. 在 iOS 上架时,需要将 plist 文件上传到苹果的 ...

  7. CopyOnWriteArrayList的使用和优缺点

    CopyOnWriteArrayList允许并发读,读操作无锁,性能较高: 而写操作(含删除),比如向容器中添加/删除一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的 ...

  8. 【解决方法】windos server 2019 在批量创建DNS的正向与反向记录时,提示报错: >Command failed: ERROR_ACCESS_DENIED 5 0x5

    目录-快速跳转 问题描述 原因分析: 解决方案: 附言: 问题描述 操作环境与场景: 在 VM 内 windos server 2019 在批量创建DNS的正向与反向记录时,提示报错: Command ...

  9. JS中内存泄漏的几种情况

    JavaScript 中的内存泄漏是指程序中使用的内存不再被需要却没有被释放,最终导致浏览器或者 Node.js 进程使用的内存越来越大,直到程序崩溃或者系统运行缓慢. 在 JavaScript 中, ...

  10. vue中点击其他任意位置关闭弹框

    mounted() { //点击任意位置关闭区域弹窗 document.addEventListener('click', (e) => { //获取弹窗对象 const userCon = d ...