全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

Go语言中三种不同md5计算方式的性能比较

前言

本文主要介绍的是三种不同的 md5 计算方式,其实区别是读文件的不同,也就是磁盘 I/O, 所以也可以举一反三用在网络 I/O 上。下面来一起看看吧。

ReadFile

先看第一种, 简单粗暴:

func md5sum1(file string) string {
 data, err := ioutil.ReadFile(file)
 if err != nil {
 return ""
 }

 return fmt.Sprintf("%x", md5.Sum(data))
}

之所以说其粗暴,是因为 ReadFile 里面其实调用了一个 readall, 分配内存是最多的。

Benchmark 来一发:

var test_path = "/path/to/file"
func BenchmarkMd5Sum1(b *testing.B) {
 for i := 0; i < b.N; i++ {
 md5sum1(test_path)
 }
}
go test -test.run=none -test.bench="^BenchmarkMd5Sum1$" -benchtime=10s -benchmem

BenchmarkMd5Sum1-4 300 43704982 ns/op 19408224 B/op 14 allocs/op
PASS
ok tmp 17.446s

先说明下,这个文件大小是 19405028 字节,和上面的 19408224 B/op 非常接近, 因为 readall 确实是分配了文件大小的内存,代码为证:

ReadFile 源码

// ReadFile reads the file named by filename and returns the contents.
// A successful call returns err == nil, not err == EOF. Because ReadFile
// reads the whole file, it does not treat an EOF from Read as an error
// to be reported.
func ReadFile(filename string) ([]byte, error) {
 f, err := os.Open(filename)
 if err != nil {
 return nil, err
 }
 defer f.Close()
 // It's a good but not certain bet that FileInfo will tell us exactly how much to
 // read, so let's try it but be prepared for the answer to be wrong.
 var n int64

 if fi, err := f.Stat(); err == nil {
 // Don't preallocate a huge buffer, just in case.
 if size := fi.Size(); size < 1e9 {
 n = size
 }
 }
 // As initial capacity for readAll, use n + a little extra in case Size is zero,
 // and to avoid another allocation after Read has filled the buffer. The readAll
 // call will read into its allocated internal buffer cheaply. If the size was
 // wrong, we'll either waste some space off the end or reallocate as needed, but
 // in the overwhelmingly common case we'll get it just right.
 
 // readAll 第二个参数是即将创建的 buffer 大小
 return readAll(f, n+bytes.MinRead)
}

func readAll(r io.Reader, capacity int64) (b []byte, err error) {
 // 这个 buffer 的大小就是 file size + bytes.MinRead 

 buf := bytes.NewBuffer(make([]byte, 0, capacity))
 // If the buffer overflows, we will get bytes.ErrTooLarge.
 // Return that as an error. Any other panic remains.
 defer func() {
 e := recover()
 if e == nil {
 return
 }
 if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
 err = panicErr
 } else {
 panic(e)
 }
 }()
 _, err = buf.ReadFrom(r)
 return buf.Bytes(), err
}

io.Copy

再看第二种,

func md5sum2(file string) string {
 f, err := os.Open(file)
 if err != nil {
 return ""
 }
 defer f.Close()

 h := md5.New()

 _, err = io.Copy(h, f)
 if err != nil {
 return ""
 }

 return fmt.Sprintf("%x", h.Sum(nil))
}

第二种的特点是:使用了 io.Copy。 在一般情况下(特殊情况在下面会提到),io.Copy 每次会分配 32 *1024 字节的内存,即32 KB, 然后咱看下 Benchmark 的情况:

func BenchmarkMd5Sum2(b *testing.B) {

 for i := 0; i < b.N; i++ {
 md5sum2(test_path)
 }
}
$ go test -test.run=none -test.bench="^BenchmarkMd5Sum2$" -benchtime=10s -benchmem

BenchmarkMd5Sum2-4 500 37538305 ns/op 33093 B/op 8 allocs/op
PASS
ok tmp 22.657s

32 * 1024 = 32768, 和 上面的 33093 B/op 很接近。

io.Copy + bufio.Reader

然后再看看第三种情况。

这次不仅用了 io.Copy,还用了 bufio.Reader。 bufio 顾名思义, 即 buffered I/O, 性能相对要好些。bufio.Reader 默认会创建 4096 字节的 buffer。

func md5sum3(file string) string {
 f, err := os.Open(file)
 if err != nil {
 return ""
 }
 defer f.Close()
 r := bufio.NewReader(f)

 h := md5.New()

 _, err = io.Copy(h, r)
 if err != nil {
 return ""
 }

 return fmt.Sprintf("%x", h.Sum(nil))

}

看下 Benchmark 的情况:

func BenchmarkMd5Sum3(b *testing.B) {
 for i := 0; i < b.N; i++ {
 md5sum3(test_path)
 }
}
$ go test -test.run=none -test.bench="^BenchmarkMd5Sum3$" -benchtime=10s -benchmem
BenchmarkMd5Sum3-4 300 42589812 ns/op 4507 B/op 9 allocs/op
PASS
ok tmp 16.817s

上面的 4507 B/op 是不是和 4096 很接近? 那为什么 io.Copy + bufio.Reader 的方式所用内存会比单纯的 io.Copy 占用内存要少一些呢? 上文也提到, 一般情况下 io.Copy 每次会分配 32 *1024 字节的内存,那特殊情况是? 答案在源码中。

一起看看 io.Copy 相关源码:

func Copy(dst Writer, src Reader) (written int64, err error) {
 return copyBuffer(dst, src, nil)
}

// copyBuffer is the actual implementation of Copy and CopyBuffer.
// if buf is nil, one is allocated.
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
 // If the reader has a WriteTo method, use it to do the copy.
 // Avoids an allocation and a copy.

 // hash.Hash 这个 Writer 并没有实现 WriteTo 方法,所以不会走这里
 if wt, ok := src.(WriterTo); ok {
 return wt.WriteTo(dst)
 }
 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
 // 而 bufio.Reader 实现了 ReadFrom 方法,所以,会走这里
 if rt, ok := dst.(ReaderFrom); ok {
 return rt.ReadFrom(src)
 }
 
 if buf == nil {
 buf = make([]byte, 32*1024)
 }
 for {
 nr, er := src.Read(buf)
 if nr > 0 {
 nw, ew := dst.Write(buf[0:nr])
 if nw > 0 {
 written += int64(nw)
 }
 if ew != nil {
 err = ew
 break
 }
 if nr != nw {
 err = ErrShortWrite
 break
 }
 }
 if er == EOF {
 break
 }
 if er != nil {
 err = er
 break
 }
 }
 return written, err
}

从上面的源码来看, 用 bufio.Reader 实现的 io.Reader 并不会走默认的 buffer创建路径,而是提前返回了,使用了 bufio.Reader 创建的 buffer, 这也是使用了 bufio.Reader 分配的内存会小一些。

当然如果你希望 io.Copy 也分配小一点的内存,也是可以做到的,不过是用 io.CopyBuffer, buf 就创建一个 4096 的 []byte 即可, 就跟 bufio.Reader 区别不大了。

看看是不是这样:

// Md5Sum2 用 CopyBufer 重新实现,buf := make([]byte, 4096)
BenchmarkMd5Sum2-4  500 38484425 ns/op 4409 B/op  8 allocs/op
BenchmarkMd5Sum3-4  500 38671090 ns/op 4505 B/op  9 allocs/op

从结果来看, 分配的内存相差不大,毕竟实现不一样,不可能一致。

那下次如果你要写一个下载大文件的程序,你还会用 ioutil.ReadAll(resp.Body) 吗?

最后整体对比下 Benchmark 的情况:

$ go test -test.run=none -test.bench="." -benchtime=10s -benchmem
testing: warning: no tests to run
BenchmarkMd5Sum1-4  300 42551920 ns/op 19408230 B/op  14 allocs/op
BenchmarkMd5Sum2-4  500 38445352 ns/op 33089 B/op  8 allocs/op
BenchmarkMd5Sum3-4  500 38809429 ns/op 4505 B/op  9 allocs/op
PASS
ok tmp 63.821s

小结

这三种不同的 md5 计算方式在执行时间上都差不多,区别最大的是内存的分配上;

bufio 在处理 I/O 还是很有优势的,优先选择;

尽量避免 ReadAll 这种用法。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。


# go  # md5  # 加密  # 性能  # md5加密  # 深入了解Golang 哈希算法之MD5、SHA-1和SHA-256  # Go实现MD5加密的三种方法小结  # golang中字符串MD5生成方式总结  # Golang 获取文件md5校验的方法以及效率对比  # Golang的md5 hash计算操作  # Go语言对字符串进行MD5加密的方法  # Go语言MD5加密用法实例  # 解决go获取文件md5值不正确的问题  # 的是  # 如果你  # 用了  # 使用了  # 第二种  # 是因为  # 不可能  # 最多  # 很有  # 执行时间  # 你还  # 不过是  # 第二个  # 三种  # 大了  # 顾名思义  # 再看  # 这篇文章  # 上都  # 会用 


相关文章: 已有域名建站全流程解析:网站搭建步骤与建站工具选择  如何选购建站域名与空间?自助平台全解析  网站制作公司排行榜,四大门户网站排名?  广州顶尖建站服务:企业官网建设与SEO优化一体化方案  建站主机无法访问?如何排查域名与服务器问题  html制作网站的步骤有哪些,iapp如何添加网页?  小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?  韩国服务器如何优化跨境访问实现高效连接?  单页制作网站有哪些,朋友给我发了一个单页网站,我应该怎么修改才能把他变成自己的呢,请求高手指点迷津?  江苏网站制作公司有哪些,江苏书法考级官方网站?  如何制作算命网站,怎么注册算命网站?  如何快速搭建支持数据库操作的智能建站平台?  建站主机服务器选型指南与性能优化方案解析  公司网站制作费用多少,为公司建立一个网站需要哪些费用?  建站主机选购指南:核心配置优化与品牌推荐方案  如何在阿里云完成域名注册与建站?  网站规划与制作是什么,电子商务网站系统规划的内容及步骤是什么?  娃派WAP自助建站:免费模板+移动优化,快速打造专业网站  广东企业建站网站优化与SEO营销核心策略指南  如何在Golang中使用replace替换模块_指定本地或远程路径  猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?  品牌网站制作公司有哪些,买正品品牌一般去哪个网站买?  深圳网站制作公司好吗,在深圳找工作哪个网站最好啊?  如何在腾讯云服务器快速搭建个人网站?  建站之星代理费用多少?最新价格详情介绍  ,有什么在线背英语单词效率比较高的网站?  零服务器AI建站解决方案:快速部署与云端平台低成本实践  常州自助建站:操作简便模板丰富,企业个人快速搭建网站  正规网站制作公司有哪些,目前国内哪家网页网站制作设计公司比较专业靠谱?口碑好?  网站专业制作公司有哪些,做一个公司网站要多少钱?  h5网站制作工具有哪些,h5页面制作工具有哪些?  制作网站的软件下载免费,今日头条开宝箱老是需要下载怎么回事?  如何安全更换建站之星模板并保留数据?  相册网站制作软件,图片上的网址怎么复制?  建站主机选购指南与交易推荐:核心配置解析  新网站制作渠道有哪些,跪求一个无线渠道比较强的小说网站,我要发表小说?  重庆网站制作公司哪家好,重庆中考招生办官方网站?  如何通过万网虚拟主机快速搭建网站?    制作充值网站的软件,做人力招聘为什么要自己交端口钱?  高端智能建站公司优选:品牌定制与SEO优化一站式服务  如何通过cPanel快速搭建网站?  如何在万网自助建站中设置域名及备案?  如何在香港免费服务器上快速搭建网站?  网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?  网站制作需要会哪些技术,建立一个网站要花费多少?  济南网站制作的价格,历城一职专官方网站?  制作网站建设的公司有哪些,网站建设比较好的公司都有哪些?  网页设计与网站制作内容,怎样注册网站?  实惠建站价格推荐:2025年高性价比自助建站套餐解析 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。