golang bufio、ioutil讀檔案的速度比較(效能測試)和影響因素分析

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

前言

golang讀取檔案的方式主要有4種:

  1. 使用File內建的Read方法
  2. 使用bufio庫的Read方法
  3. 使用io/ioutil庫的ReadAll()
  4. 使用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 mainimport (    "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-golangfunc 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 mainimport(    "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()
  • 不同業務情境,選用不同的讀取方式
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.