golang bufio、ioutil读文件的速度比较(性能测试)和影响因素分析
前言
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[i] = 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<1024;i++ { //复制1024遍
copy(bb[len(b)*i:len(b)*(i+1)],b)
}
//fmt.Printf("%s",b)
ioutil.WriteFile(path,bb,0666)
}
func main() {
flag.Parse()
filesizeMB,err :=strconv.Atoi(flag.Arg(0)) //1-500MB大小的文件
if err != nil{panic(err)}
if filesizeMB > 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 size is %d MB.",filesizeMB)
}
测试代码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, size)
- 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()
- 不同业务场景,选用不同的读取方式
golang bufio、ioutil读文件的速度比较(性能测试)和影响因素分析的更多相关文章
- GoLang几种读文件方式的比较
GoLang提供了很多读文件的方式,一般来说常用的有三种.使用Read加上buffer,使用bufio库和ioutil 库. 那他们的效率如何呢?用一个简单的程序来评测一下: package main ...
- GO语言的进阶之路-Golang字符串处理以及文件操作
GO语言的进阶之路-Golang字符串处理以及文件操作 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我们都知道Golang是一门强类型的语言,相比Python在处理一些并发问题也 ...
- golang学习笔记 ----读写文件
使用io/ioutil进行读写文件 ioutil包 其中提到了两个方法: func ReadFile func ReadFile(filename string) ([]byte, error) Re ...
- golang bufio解析
golang bufio 当进行频繁地对少量数据读写时会占用IO,造成性能问题.golang的bufio库使用缓存来一次性进行大块数据的读写,以此降低IO系统调用,提升性能. 在Transport中可 ...
- Python之路 day2 按行读文件
#1. 最基本的读文件方法: # File: readline-example-1.py file = open("sample.txt") while 1: line = fil ...
- java的读文件操作
java读取文件内容,可以作如下理解: 首先获得一个文件句柄,File file = new File():file即为文件句柄.两人之间联通电话网络了,就可以开始打电话了. 通过这条线路读取甲方的信 ...
- PHP使用feof()函数读文件的方法
这篇文章主要介绍了PHP使用feof()函数读文件的方法,以实例形式对比了正确与错误的用法,阐明了feof()函数的使用技巧,需要的朋友可以参考下 本文实例讲述了PHP使用feof()函数读文件的方法 ...
- Java基础之读文件——使用输入流读取二进制文件(StreamInputFromFile)
控制台程序,读取Java基础之读文件部分(StreamOutputToFile)写入的50个fibonacci数字. import java.nio.file.*; import java.nio.* ...
- c++中ifstream读文件的问题(关于eof())
今天帮别人找BUG,是一段关于c++读写文件的问题,使用的是ifstream与outstream类,关于ofstream与ifstream的用法,此处不再獒述,见代码: #include<ios ...
随机推荐
- PHP5.4 需要添加的configure参数
./configure --prefix=/usr/local/php --with-gd=/usr/local/gd --with-jpeg-dir=/usr/local/jpeg --with-p ...
- vsftp 虚拟用户测试
1.创建用于进行FTP验证的帐号密码数据库文件,单数行为账户名,偶数行为密码.[root@rhel1 vsftpd]# vi /etc/vsftpd/vuser.listuser1123456user ...
- 【CAS单点登录视频教程】 第02集 -- 安装CAS
目录 ----------------------------------------- [CAS单点登录视频教程] 第06集[完] -- Cas认证 学习 票据认证FormsAuthenticati ...
- 怎么样删除linux里带括号的文件?
可以用 rm -i * 就是对每个删除都提示用户是否确定,这样在出现 (2).htaccess 的时候你回答y就可以了,其他回答n,当删除了 (2).htaccess这个文件之后,就可以用Ctrl+C ...
- Git: fatal: Pathspec is in submodule
出现是问题: git提交代码是出现fatal: Path 'directory/file' is in submodule 'directory''错误 Removing the directory ...
- Out of memory error : GC overhead limit exceeded
GC overhead limit exceeded 是指垃圾回收器通过分析当前内存使用趋势,提前抛出异常而不是真正等到内存耗尽再抛出异常.如果真正等到内存耗尽再抛出异常,可能的后果是:我们连保存重要 ...
- memcached全面剖析--2.理解memcached的内存存储
下面是<memcached全面剖析>的第二部分. 发表日:2008/7/9 作者:前坂徹(Toru Maesaka) 原文链接:http://gihyo.jp/dev/feature/01 ...
- React(0.13) 定义一个动态的组件(属性)
<!DOCTYPE html> <html> <head> <title>React JS</title> <script src=& ...
- SQL数据缓存依赖总结
以前只听过SQL server数据缓存依赖,但一直没使用,由于项目需要,才研究了一番,发现了一个很诡异的问题,竟然是一个操作顺序问题导致的. SQL server数据缓存依赖有两种实现模式,轮询模式, ...
- C# WinForm开发系列 - 文章索引
该系列主要整理收集在使用C#开发WinForm应用文章及相关代码, 平时看到大家主要使用C#来开发Asp.Net应用,这方面的文章也特别多,而关于WinForm的文章相对少很多,而自己对WinForm ...