基准测试的缓冲写入
在这节,我们将使用 writingBU.go
的代码来探索写缓冲的大小如何影响整个写操作的性能,它分为五部分来介绍。
writingBU.go
程序使用随机生成的数据来产生虚拟文件。这个程序的变量是缓冲的大小和输出文件的大小。
writingBU.go
的第一部分如下:
package main
import(
"fmt"
"math/rand"
"os"
"strconv"
)
var BUFFERSIZE int
var FILESIZE int
func random(min, max int) int {
return rand.Intn(max-min) + min
}
writingBU.go
的第二段代码如下:
func createBuffer(buf *[]byte, count int) {
*buf = make([]byte, count)
if count == 0 {
return
}
for i := 0; i < count; i++ {
intByte := byte(random(0, 100))
if len(*buf) > count {
return
}
*buf = append(*buf, intByte)
}
}
writingBU.go
的第三部分如下:
func Create(dst string, b, f int) error {
_, err := os.Stat(dst)
if err == nil {
return fmt.Error("File %s already exists.", dst)
}
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
if err != nil {
panic(err)
}
buf := make([]byte, 0)
for {
createBuffer(&buff, b)
buf = buf[:b]
if _, err := destination.Write(buf); err != nil {
return err
}
if f < 0 {
break
}
f = f - len(buf)
}
return err
}
程序中的 Create()
函数做了所有工作,他是需要进行基准测试的函数。
注意如果缓冲大小和文件大小不是 Create()
函数的签名的一部分,在给 Create()
函数写基准测试函数时您将遇到问题,因为您需要使用 BUFFERSIZE
和 FILESIZE
全局变量,它们都是在 writingBU.go
的 main()
函数中初始化的。
这将是一个难点在 writingBU_test.go
文件中。这意味着为了给一个函数创建一个基准测试,您应该在您写代码时就考虑这个问题。
writingBU.go
的第四部分如下:
func main() {
if len(os.Args) != 3 {
fmt.Println("Need BUFFERSIZE FILESIZE!")
return
}
output := "/tmp/randomFile"
BUFFERSIZE,_ = strconv.Atoi(os.Args[1])
FILESIZE, _ = strconv.Atoi(os.Args[2])
err := Create(output, BUFFERSIZE, FILESIZE)
if err != nil {
fmt.Println(err)
}
writingBU.go
的其余代码如下:
err = os.Remove(output)
if err != nil {
fmt.Println(err)
}
}
尽管在 main()
函数里调用 os.Remove()
删除了临时文件,但没有在基准测试函数中调用它,在基准测试函数中调用它比较简单,所以这不是问题。
在一台有 SSD 硬盘的 macOS High Sierra 机器上执行 writingBU.go
俩次,用 time(1)
工具来检测程序产生如下输出但速度:
$ time go run writingBU.go 1 100000
real 0m1.193s
user 0m0.349s
sys 0m0.809s
$ time go run writingBU.go 10 100000
real 0m0.283s
user 0m0.195s
sys 0m0.228s
尽管这显示出写缓冲的大小对程序的性能起到关键作用,但我们需要更具体更准确。因此,我们来写基准测试函数存储为 writingBU_test.go
。
writingBU_test.go
的第一部分如下:
package main
import (
"fmt"
"os"
"testing"
)
var ERR error
func benchmarkCreate(b *testing.B, buffer, filesize int) {
var err error
for i := 0; i < b.N; i++ {
err = Create("/tmp/random", buffer, filesize)
}
ERR = err
err = os.Remove("/tmp/random")
if err != nil {
fmt.Println(err)
}
}
您会记得这不是一个有效的基准测试函数。
writingBU_test.go
的第二段代码如下:
func Benchmark1Create(b *testing.B) {
benchmarkCreate(b, 1, 1000000)
}
func Benchmark2Create(b *testing.B) {
benchmarkCreate(b, 2, 1000000)
}
writingBU_test.go
的其余代码如下:
func Benchmark4Create(b *testing.B) {
benchmarkCreate(b, 4, 1000000)
}
func Benchmark10Create(b *testing.B) {
benchmarkCreate(b, 10, 1000000)
}
func Benchmark1000Create(b *testing.B) {
benchmarkCreate(b, 1000, 1000000)
}
这里我们写了五个基准测试函数来检测 benchmarkCreate()
函数的性能,它用写缓冲大小变量检测 Create()
函数的性能。
对 writingBU.go
和 writingBU_test.go
文件执行 go test
将产生如下输出:
下面的输出也检测了基准测试函数的内存分配:
现在来解释一下这俩个 go tesst
命令的输出。
很明显使用一个大小为 1 个字节的写缓冲是完全无效的并且缓冲所有的操作。另外,这样的缓冲大小需要更多的内存操作,这也使程序运行的更慢!
使用 2 个字节的缓冲可以整个程序速度提升 2 倍,这是好事。然而,这仍然很慢。这同样适用于 4 个字节的写缓冲。
当决定用 10 个字节的写缓冲时,这会变的更快。最后,这个结果显示使用 1,000 字节的写缓冲没有比使用 10 字节的快 100 倍,这意味着在速度和写缓冲大小之间的最佳点是在这俩个值之间。