首页 > 其他分享 >【转】golang bufio、ioutil读文件的速度比较(性能测试)和影响因素分析

【转】golang bufio、ioutil读文件的速度比较(性能测试)和影响因素分析

时间:2023-02-20 15:37:18浏览次数:54  
标签:ioutil err Read 测试 golang bufio time

golang读取文件的方式主要有4种:

  1. 使用File自带的Read方法
  2. 使用bufio库的Read方法
  3. 使用io/ioutil库的ReadAll()
  4. 使用io/ioutil库的ReadFile() 使用io/ioutil库的ReadFile()

关于前3种方式的速度比较,我最早是在 GoLang几种读文件方式的比较 看过,但在该blog的评论区有人(study_c)提出了质疑,并提供了测试代码。根据该代码的测试,结果应该是

bufio > ioutil.ReadAll > File自带Read

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

  • Read()每次读取的块的大小对结果也是有影响的
  • 连续测试同一个文件,会从系统缓存或是SSD缓存加载文件,后面的测试结果会被加速

所以本文的性能测试就是基于study_c的代码的基础上做了修改,尝试测试不同块大小对结果的影响,并增加对ioutil.ReadFile()的测试,还有随机生成文件以应对缓存影响公平性。

性能测试

测试环境

CPU: i5-6300HQ CPU: i5 - 6300总部
MEM: 12GB MEM: 12 gb
DSK: SANDISK Extreme PRO SSD 480GB
DSK: SANDISK Extreme PRO SSD 480GB
OS : WIN 10 64bit 操作系统:win10 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的大小,这个直接决定了读取的快慢:

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

结论

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

标签:ioutil,err,Read,测试,golang,bufio,time
From: https://www.cnblogs.com/acodedreamer/p/17137602.html

相关文章

  • 【转】golang的log.Fatal()和panic()函数的区别
    golang的log.Fatal()和panic()函数的区别在讲两者区别之前我们先看一下os.Exit()函数的定义:funcExit(codeint)Exitcausesthecurrentprogramtoexitwiththe......
  • golang拾遗:实现一个不可复制类型
    这是golang拾遗系列的第六篇。这个系列主要用来记录一些平时不常见的知识点,偶尔也会实现些有意思的小功能,比如这篇。golang拾遗系列目录:golang拾遗:指针和接口golang拾......
  • Golang微服务(一)
    Golang微服务(一)目录Golang微服务(一)一、protobuf常规使用及踩坑记录1.类型映射关系及零值2.go_package设置3.protobuf的字段编号4.proto文件的import5.protobuf的message嵌......
  • golang 入门(十) 异常处理
    1、recovery捕获异常代码在运行的时候,总会遇到错误。有的时候我们会希望程序遇到错误以后继续运行后面的流程,而不是直接异常退出。在Python中,使用tryexcept组合实现这种需......
  • Golang基础-Runes
    rune与stringTherunetypeinGoisanaliasforint32.Giventhisunderlyingint32type,therunetypeholdsasigned32-bitintegervalue.However,unlikean......
  • golang 面向对象
    1.张老太养了两只猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名字......
  • Golang基础-Maps
    常见用法varagesmap[string]int//只声明不初始化是nil,赋值会panic:assignmenttoentryinnilmapfmt.Println(ages==nil)//"true"fmt.Println(len(ag......
  • Golang基础-Time
    常用函数t,err:=time.Parse(layout,date)//time.Time,errort:=time.Date(1995,time.September,22,13,0,0,0,time.UTC)formatedTime:=t.Format("Mon,01/02/2......
  • day6 golang-标准库(随时更新)
    time时间库 packagemainimport( "fmt" "time")funcmain(){ t:=time.Now() //time.Timetime.Date(2023,time.February,19,14,38,1,393023500,ti......
  • Golang接口
    理解go中的接口,首先从java的入手圆形和长方形是不同形状,因此他们是两个类,circle和rectangle在java中,他们应当拥有一个公共父类,即形状shape无论哪种形状,都应当是可计算面......