Golang 逐行读取大文件性能对比

51reboot · 2017-09-13 05:34:50 · 7864 次点击 · 预计阅读时间 2 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2017-09-13 05:34:50 的文章,其中的信息可能已经有所发展或是发生改变。


前 言

BUFIO 是什么?
BUFIO 是用来驱动 I/O 列内的专用时钟网络,这个专用的时钟网络独立于全局时钟资源,适合采集源同步数据。BUFIO 只能由位于同一时钟区域的 Clock-Capable I/O驱动。一个时钟区域有4个 BURIO,其中的2个可以驱动相邻区域的 I/O 时钟网络。BUFIO 不能驱动逻辑资源(CLB、BRAM等),因为 I/O 时钟网络只存在于 I/O 列中。

简单点就是:

  • bufio 包实现了带缓存的 I/O 操作

  • 它封装一个 io.Reader 或 io.Writer 对象

  • 使其具有缓存和一些文本读写功能

本文主要来对比一下 BUFIO 中的 ReadString 和 ReadLine 函数的性能。

注:测试代码忽略读取内容和错误处理

ReadString 函数

ReadString 代码:

func ReadString(filename string) {
    f, _ := os.Open(filename)
    defer f.Close()
    r := bufio.NewReader(f)
    for {
        _, err := r.ReadString('\n') 
        if err != nil {
            break
        }
    }
}

ReadLine 函数

ReadLine 代码:

func ReadLine(filename string) {
    f, _ := os.Open(filename)
    defer f.Close()
    r := bufio.NewReader(f)
    for {
        _, err := readLine(r)
        if err != nil {
            break
        }
    }

}

此函数主要解决单行字节数大于4096的情况

func readLine(r *bufio.Reader) (string, error) {
    line, isprefix, err := r.ReadLine()
    for isprefix && err == nil {
        var bs []byte
        bs, isprefix, err = r.ReadLine()
        line = append(line, bs...)
    }
    return string(line), err
}

注: 测试文件 log 每行字节数均大于4096

性 能 对 比

以上两种方式分别读取10G/20G/30G文件的耗时如下:

读取10G文件耗时

readstring:30.717832767s
readline:27.358268244s

读取20G文件耗时

readstring:59.937901346s
readline:54.871384854s

**读取30G文件耗时******

readstring:1m21.657831495s
readline:1m13.222376352s

结 论

ReadLine 读取文件更快,原因是由于 ReadString 后端调用 ReadBytes,而 ReadBytes 多次使用 copy 方法造成大量耗时。

测试代码如下:

package main

import (
    "bufio"
    "fmt"
    "os"
    "time"
)
func main() {
    filename := "./log"
    s := time.Now()
    ReadString(filename)
    e1 := time.Now()
    fmt.Printf("readstring:%v\n", e1.Sub(s))
    ReadLine(filename)
    e2 := time.Now()
    fmt.Printf("readline:%v\n", e2.Sub(e1))
}
func ReadString(filename string) {
    f, _ := os.Open(filename)
    defer f.Close()
    r := bufio.NewReader(f)
    for {
        _, err := r.ReadString('\n') //忽略内容
        if err != nil {
            break
        }

    }
}
func ReadLine(filename string) {
    f, _ := os.Open(filename)
    defer f.Close()
    r := bufio.NewReader(f)
    for {
        _, err := readLine(r)
        if err != nil {
            break
        }
    }

}
func readLine(r *bufio.Reader) (string, error) {
    line, isprefix, err := r.ReadLine()
    for isprefix && err == nil {
        var bs []byte
        bs, isprefix, err = r.ReadLine()
        line = append(line, bs...)
    }
    return string(line), err
}

技术交流QQ群:368573673

有兴趣的可以关注我们的微信公众号:Reboot51


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:51reboot

查看原文:Golang 逐行读取大文件性能对比

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

7864 次点击  ∙  2 赞  
加入收藏 微博
4 回复  |  直到 2020-03-24 03:01:09
anko
anko · #1 · 5年之前

受硬件影响较大,不说明配置确实难以比较

anko
anko · #2 · 5年之前

同时你把读取的东西忽略掉也是受影响的,10g东西30s是不可能的事情

anko
anko · #3 · 5年之前

这是我的测试,读取2g文件然后处理接着写入文件,一共花了36点几s,所以你的测试是不准确的,我的是固态硬盘,cpu差点,下面是代码:

package main

import (
    "bufio"
    "fmt"
    "os"
    "time"
)

func main() {
    start := time.Now()
    ReadLine(`C:\Users\Administrator\Desktop\go_pro\src\io_pro\test12\test\阳光电影www.ygdy8.com.鬼影特攻:以暴制暴.BD.1080p.中英双字幕.mkv`)
    end := time.Now()
    fmt.Println(end.Sub(start))
}
func ReadLine(filename string) {

    f, _ := os.Open(filename)
    defer f.Close()
    r := bufio.NewReader(f)
    newfile, err := os.OpenFile(`C:\Users\Administrator\Desktop\go_pro\src\io_pro\test12\test\阳光电影www.ygdy8.com.鬼影特攻:以暴制暴.BD.1080p.中英双字幕1111.mkv`, os.O_CREATE|os.O_WRONLY, 0666)
    checkErr(err)
    defer newfile.Close()
    for {
        s, err := readLine(r)
        if err != nil {
            break
        }
        newfile.WriteString(s)

    }

}

func readLine(r *bufio.Reader) (string, error) {
    line, isprefix, err := r.ReadLine()
    for isprefix && err == nil {
        var bs []byte
        bs, isprefix, err = r.ReadLine()
        line = append(line, bs...)
    }
    return string(line), err
}
func checkErr(err error) {
    if err != nil {
        fmt.Println(err)
    }
}
anko
anko · #4 · 5年之前

下面同样的读取2g的同一个文件,我给出分段读取分段处理的代码,是我认为比较快的代码了,时间一般是13s左右,竞争大则需要花费26s左右,还可以优化的!采用异步读取和写入!望能抛砖引玉:

package main

import (
    "fmt"
    "io"
    "os"
    "sync"
    "time"

    //"sync"
)

var L sync.Mutex
var L2 sync.Mutex
var byteNum =4096
var rOff, wOff = -byteNum, -byteNum
var retResev, retSend = false, false

var wg = sync.WaitGroup{}
var ch = make(chan []byte, 8)

func main() {

    start := time.Now()
    newfile, err := os.OpenFile(`C:\Users\Administrator\Desktop\go_pro\src\io_pro\test12\test\阳光电影www.ygdy8.com.鬼影特攻:以暴制暴.BD.1080p.中英双字幕1111.mkv`, os.O_CREATE|os.O_WRONLY, 0666)
    checkErr(err)
    defer newfile.Close()

    file, e := os.Open(`C:\Users\Administrator\Desktop\go_pro\src\io_pro\test12\test\阳光电影www.ygdy8.com.鬼影特攻:以暴制暴.BD.1080p.中英双字幕.mkv`)
    checkErr(e)
    defer file.Close()

    for i := 0; i < 4; i++ {
        //接收数据
        wg.Add(1)
        go resevCh(newfile)

        wg.Add(1)
        //发送数据
        go SendCh(file)
    }
    wg.Wait()
    end := time.Now()

    fmt.Println(end.Sub(start))
    fmt.Println("主程序退出")
}

func resevCh(newfile *os.File) {
    L2.Lock() //必须是与上面不同的锁
    defer func() {
        //fmt.Println("写入子程序退出....")
        L2.Unlock()
        wg.Done()
    }()
    //限制口,防止在其中一个g程写入文件完成后然而其他g程还没结束
    if retResev {
        return
    }
    for !retResev {
        ls, ok := <-ch
        if ok {
            wOff += byteNum
            //var n1 int
            _, err := newfile.WriteAt(ls, int64(wOff))
            //fmt.Println("len(ls):", len(ls))
            //fmt.Println("线程写入resevCh的字节数为:", n1)
            checkErr(err)

        } else {
            retResev = true
        }

    }

}

func SendCh(file *os.File) {

    L.Lock()
    defer func() {
        fmt.Println("读取SendCh子程序退出!!!")
        L.Unlock()
        wg.Done()
    }()
    //限制口,防止在其中一个g程读取文件完成后然而其他g程还没结束
    if retSend {
        return
    }
    for !retSend {
        rOff += byteNum
        ls := make([]byte, byteNum) //每个g程都维护一个这样的结构

        n, err := file.ReadAt(ls, int64(rOff))
        //fmt.Println("某某线程读取了的字节数为:", n)
        checkErr(err)
        ch <- ls[:n] //通信
        if err == io.EOF {
            retSend = true
            close(ch) //务必要关闭,而且只能由一个g程关闭,而不是所有的g程,
            //所以不能放到defer func中去
            fmt.Println("关闭ch")
            return
        }

    }

}

func checkErr(err error) {

    if err != nil {
        fmt.Println(err)
    }
}
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传