go - 使用 bufio.NewScanner 提高阅读性能
问题描述
一个服务于一个目的的简单程序:
- 逐行读取脚本文件,创建一个字符串,同时忽略任何空白的新行或注释(包括 shebang)。添加一个';' 如果需要,在一行的末尾。(我知道,我知道,反斜杠和 & 号等)
我的问题是:
如何提高这个小程序的性能?在另一个答案中,我读过关于使用scanner.Bytes()
而不是scanner.Text()
,但这似乎不可行,因为我想要的是字符串。
带有测试文件的示例代码:https: //play.golang.org/p/gzSTLkP3BoB
这是一个简单的程序:
func main() {
file, err := os.Open("./script.sh")
if err != nil {
log.Fatalln(err)
}
defer file.Close()
var a strings.Builder
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines := scanner.Text()
switch {
case lines == "" || lines[:1] == "#":
continue
case lines[len(lines)-1:] != ";":
a.WriteString(lines + "; ")
default:
a.WriteString(lines + " ")
}
}
fmt.Println(a.String())
}
解决方案
我用strings.Builder
andioutil.ReadAll
来提高性能。当您处理小型 shell 脚本时,我认为一次读取文件不应该对内存造成压力(我使用过ioutil.ReadAll
)。我还只分配了一次,以便为strings.Builder
减少分配提供足够的存储空间。
- doFast:更快的实现
- doSlow:较慢的实现(您最初所做的)
现在,让我们看一下基准测试结果:
goos: darwin
goarch: amd64
pkg: test
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
BenchmarkDoFast-8 342602 3334 ns/op 1280 B/op 3 allocs/op
BenchmarkDoSlow-8 258896 4408 ns/op 4624 B/op 8 allocs/op
PASS
ok test 2.477s
我们可以看到,这doFast
不仅更快,而且分配更少。衡量的指标越低越好。
package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
)
func open(filename string) (*os.File, error) {
return os.Open(filename)
}
func main() {
fd, err := open("test.sh")
if err != nil {
panic(err)
}
defer fd.Close()
outputA, err := doFast(fd)
if err != nil {
panic(err)
}
fd.Seek(0, 0)
outputB, err := doSlow(fd)
if err != nil {
panic(err)
}
fmt.Println(outputA)
fmt.Println(outputB)
}
func doFast(fd *os.File) (string, error) {
b, err := ioutil.ReadAll(fd)
if err != nil {
return "", err
}
var res strings.Builder
res.Grow(len(b))
bLines := bytes.Split(b, []byte("\n"))
for i := range bLines {
switch {
case len(bLines[i]) == 0 || bLines[i][0] == '#':
case bLines[i][len(bLines[i])-1] != ';':
res.Write(bLines[i])
res.WriteString("; ")
default:
res.Write(bLines[i])
res.WriteByte(' ')
}
}
return res.String(), nil
}
func doSlow(fd *os.File) (string, error) {
var a strings.Builder
scanner := bufio.NewScanner(fd)
for scanner.Scan() {
lines := scanner.Text()
switch {
case lines == "" || lines[:1] == "#":
continue
case lines[len(lines)-1:] != ";":
a.WriteString(lines + "; ")
default:
a.WriteString(lines + " ")
}
}
return a.String(), nil
}
注意:我没有使用bufio.NewScanner
; 是必需的吗?
推荐阅读
- simulation - 如何在 OMNET++ 中测量流量
- uml - 如何表达树结构约束
- java - 根据计算值对使用 spring JPA 分页的 DB 中的行进行排序
- javascript - .map 从 this.state 做出反应
- python - 不断更新 Tkinter 中的标签
- javascript - Vue Array prop 默认值不起作用
- amazon-web-services - toomanyrequests:您已达到拉取率限制。您可以通过身份验证和升级来增加限制
- c++ - PortAudio 中的输出
- python - 如何创建具有 2 列唯一且自动递增的模型?
- c# - DLL 中的 Microsoft.Graph 集合反序列化