设为首页 收藏本站
查看: 1473|回复: 0

[经验分享] golang bufio、ioutil读文件的速度比较(性能测试)和影响因素分析

[复制链接]

尚未签到

发表于 2018-9-21 06:06:48 | 显示全部楼层 |阅读模式
前言
  golang读取文件的方式主要有4种:


  • 使用File自带的Read方法
  • 使用bufio库的Read方法
  • 使用io/ioutil库的ReadAll()
  • 使用io/ioutil库的ReadFile()
  关于前3种方式的速度比较,我最早是在 GoLang几种读文件方式的比较 看过,但在该blog的评论区有人(study_c)提出了质疑,并提供了测试代码。根据该代码的测试,结果应该是

  bufio > ioutil.ReadAll > File自带Read

  在我反复跑study_c测试代码过程中发现几个问题或者说是影响因素:


  • Read()每次读取的块的大小对结果也是有影响的
  • 连续测试同一个文件,会从系统缓存或是SSD缓存加载文件,后面的测试结果会被加速
  所以本文的性能测试就是基于study_c的代码的基础上做了修改,尝试测试不同块大小对结果的影响,并增加对ioutil.ReadFile()的测试,还有随机生成文件以应对缓存影响公平性。

性能测试
  测试环境

  CPU: i5-6300HQ
  MEM: 12GB
  DSK: SANDISK Extreme PRO SSD 480GB
  OS : WIN 10 64bit

  测试代码1【randfiles.go】,生成1-500MB包含随机字符串的文件
  

package main  

  
import (
  
"math/rand"
  
"fmt"
  
"flag"
  
"strconv"
  
"io/ioutil"
  
)
  

  
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
  
func RandStringBytes(n int) []byte {
  
b := make([]byte, n)
  
for i := range b {
  
b = letterBytes[rand.Intn(len(letterBytes))]
  
}
  
return b
  
}
  
func RandFile(path string,filesizeMB int) {
  
b:=RandStringBytes(filesizeMB * 1024)    //生成1-500KB大小的随机字符串
  
bb := make([]byte, filesizeMB * 1024 * 1024)
  
for i:=0;i 500 {panic("too large file,>500MB")}
  
RandFile("./random1.txt",filesizeMB)
  
RandFile("./random2.txt",filesizeMB)
  
RandFile("./random3.txt",filesizeMB)
  
RandFile("./random4.txt",filesizeMB)

  
fmt.Printf("Created 4 files, each file>  
}
  

  测试代码2【speed.go】,性能测试
  

package main  

  
import(
  
"fmt"
  
"os"
  
"flag"
  
"io"
  
"io/ioutil"
  
"bufio"
  
"time"
  
"strconv"
  
)
  

  
func read1(path string,blocksize int){
  
fi,err := os.Open(path)
  
if err != nil{
  
panic(err)
  
}
  
defer fi.Close()
  
block := make([]byte,blocksize)
  
for{
  
n,err := fi.Read(block)
  
if err != nil && err != io.EOF{panic(err)}
  
if 0 ==n {break}
  
}
  
}
  

  
func read2(path string,blocksize int){
  
fi,err := os.Open(path)
  
if err != nil{panic(err)}
  
defer fi.Close()
  
r := bufio.NewReader(fi)
  
block := make([]byte,blocksize)
  
for{
  
n,err := r.Read(block)
  
if err != nil && err != io.EOF{panic(err)}
  
if 0 ==n {break}
  
}
  
}
  

  
func read3(path string){
  
fi,err := os.Open(path)
  
if err != nil{panic(err)}
  
defer fi.Close()
  
_,err = ioutil.ReadAll(fi)
  
}
  

  
func read4(path string){
  
_,err := ioutil.ReadFile(path)
  
if err != nil{panic(err)}
  
}
  

  
func main(){
  
flag.Parse()
  
file1 := "./random1.txt"
  
file2 := "./random2.txt"
  
file3 := "./random3.txt"
  
file4 := "./random4.txt"
  
blocksize,_ :=strconv.Atoi(flag.Arg(0))
  
var start,end time.Time
  
start = time.Now()
  
read1(file1,blocksize)
  
end = time.Now()
  
fmt.Printf("file/Read() cost time %v\n",end.Sub(start))
  
start = time.Now()
  
read2(file2,blocksize)
  
end = time.Now()
  
fmt.Printf("bufio/Read() cost time %v\n",end.Sub(start))
  
start = time.Now()
  
read3(file3)
  
end = time.Now()
  
fmt.Printf("ioutil.ReadAll() cost time %v\n",end.Sub(start))
  
start = time.Now()
  
read4(file4)
  
end = time.Now()
  
fmt.Printf("ioutil.ReadFile() cost time %v\n",end.Sub(start))
  
}
  

  测试结果:

  测试1:块大小为4KB,这是个常见的大小,出人意料ioutil.ReadAll()最慢
  测试2:块大小为1KB,这是前言提到的测试结果所用的块大小,与其测试结果一致
  测试3:块大小为32KB,在大块的情况下,调用Read()次数更少,bufio已经没有优势,但前两者却远快于ioutil包的两个函数
  测试4:块大小为16字节,在小块的情况下,没有缓存的文件普通Read成绩惨不忍睹

影响因素
  在查阅golang标准库的源代码后,之所以有不同的结果是与每个方法的实现相关的,最大的因素就是内部buffer的大小,这个直接决定了读取的快慢:


  • f.Read()底层实现是系统调用syscall.Read(),没有深究
  • bufio.NewReader(f)实际调用NewReaderSize(f, defaultBufSize),而defaultBufSize=4096,可以直接用bufio.NewReaderSize(f,32768)来预分配更大的缓存,缓存的实质是make([]byte,>
  • ioutil.ReadAll(f)实际调用readAll(r, bytes.MinRead),而bytes.MinRead=512,缓存的实质是bytes.NewBuffer(make([]byte, 0, 512),虽然bytes.Buffer会根据情况自动增大,但每次重新分配都会影响性能
  • ioutil.ReadFile(path)是调用readAll(f, n+bytes.MinRead),这个n取决于文件大小,文件小于10^9字节(0.93GB),n=文件大小,就是NewBuffer一个略大于文件大小的缓存,非常慷慨;大于则n=0,好惨,也就是说大于1G的文件就跟ioutil.ReadAll(f)一个样子了。
  • 但全量缓存的ReadFile为什么不如大块读取的前两者呢?我猜测是NewBuffer包装的字节数组性能当然不如裸奔的字符数组。。
结论


  • 当每次读取块的大小小于4KB,建议使用bufio.NewReader(f), 大于4KB用bufio.NewReaderSize(f,缓存大小)
  • 要读Reader, 图方便用ioutil.ReadAll()
  • 一次性读取文件,使用ioutil.ReadFile()
  • 不同业务场景,选用不同的读取方式




运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-598990-1-1.html 上篇帖子: 一分钟上手, 让 Golang 操作数据库成为一种享受 下篇帖子: golang下在终端显示字体颜色及闪烁、下划线效果的小工具
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表