基准测试的缓冲写入

在这节,我们将使用 writingBU.go 的代码来探索写缓冲的大小如何影响整个写操作的性能,它分为五部分来介绍。

writingBU.go 程序使用随机生成的数据来产生虚拟文件。这个程序的变量是缓冲的大小和输出文件的大小。

writingBU.go 的第一部分如下:

  1. package main
  2. import(
  3. "fmt"
  4. "math/rand"
  5. "os"
  6. "strconv"
  7. )
  8. var BUFFERSIZE int
  9. var FILESIZE int
  10. func random(min, max int) int {
  11. return rand.Intn(max-min) + min
  12. }

writingBU.go 的第二段代码如下:

  1. func createBuffer(buf *[]byte, count int) {
  2. *buf = make([]byte, count)
  3. if count == 0 {
  4. return
  5. }
  6. for i := 0; i < count; i++ {
  7. intByte := byte(random(0, 100))
  8. if len(*buf) > count {
  9. return
  10. }
  11. *buf = append(*buf, intByte)
  12. }
  13. }

writingBU.go 的第三部分如下:

  1. func Create(dst string, b, f int) error {
  2. _, err := os.Stat(dst)
  3. if err == nil {
  4. return fmt.Error("File %s already exists.", dst)
  5. }
  6. destination, err := os.Create(dst)
  7. if err != nil {
  8. return err
  9. }
  10. defer destination.Close()
  11. if err != nil {
  12. panic(err)
  13. }
  14. buf := make([]byte, 0)
  15. for {
  16. createBuffer(&buff, b)
  17. buf = buf[:b]
  18. if _, err := destination.Write(buf); err != nil {
  19. return err
  20. }
  21. if f < 0 {
  22. break
  23. }
  24. f = f - len(buf)
  25. }
  26. return err
  27. }

程序中的 Create() 函数做了所有工作,他是需要进行基准测试的函数。

注意如果缓冲大小和文件大小不是 Create() 函数的签名的一部分,在给 Create() 函数写基准测试函数时您将遇到问题,因为您需要使用 BUFFERSIZEFILESIZE 全局变量,它们都是在 writingBU.gomain() 函数中初始化的。

这将是一个难点在 writingBU_test.go 文件中。这意味着为了给一个函数创建一个基准测试,您应该在您写代码时就考虑这个问题。

writingBU.go 的第四部分如下:

  1. func main() {
  2. if len(os.Args) != 3 {
  3. fmt.Println("Need BUFFERSIZE FILESIZE!")
  4. return
  5. }
  6. output := "/tmp/randomFile"
  7. BUFFERSIZE,_ = strconv.Atoi(os.Args[1])
  8. FILESIZE, _ = strconv.Atoi(os.Args[2])
  9. err := Create(output, BUFFERSIZE, FILESIZE)
  10. if err != nil {
  11. fmt.Println(err)
  12. }

writingBU.go 的其余代码如下:

  1. err = os.Remove(output)
  2. if err != nil {
  3. fmt.Println(err)
  4. }
  5. }

尽管在 main() 函数里调用 os.Remove() 删除了临时文件,但没有在基准测试函数中调用它,在基准测试函数中调用它比较简单,所以这不是问题。

在一台有 SSD 硬盘的 macOS High Sierra 机器上执行 writingBU.go 俩次,用 time(1) 工具来检测程序产生如下输出但速度:

  1. $ time go run writingBU.go 1 100000
  2. real 0m1.193s
  3. user 0m0.349s
  4. sys 0m0.809s
  5. $ time go run writingBU.go 10 100000
  6. real 0m0.283s
  7. user 0m0.195s
  8. sys 0m0.228s

尽管这显示出写缓冲的大小对程序的性能起到关键作用,但我们需要更具体更准确。因此,我们来写基准测试函数存储为 writingBU_test.go

writingBU_test.go 的第一部分如下:

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "testing"
  6. )
  7. var ERR error
  8. func benchmarkCreate(b *testing.B, buffer, filesize int) {
  9. var err error
  10. for i := 0; i < b.N; i++ {
  11. err = Create("/tmp/random", buffer, filesize)
  12. }
  13. ERR = err
  14. err = os.Remove("/tmp/random")
  15. if err != nil {
  16. fmt.Println(err)
  17. }
  18. }

您会记得这不是一个有效的基准测试函数。

writingBU_test.go 的第二段代码如下:

  1. func Benchmark1Create(b *testing.B) {
  2. benchmarkCreate(b, 1, 1000000)
  3. }
  4. func Benchmark2Create(b *testing.B) {
  5. benchmarkCreate(b, 2, 1000000)
  6. }

writingBU_test.go 的其余代码如下:

  1. func Benchmark4Create(b *testing.B) {
  2. benchmarkCreate(b, 4, 1000000)
  3. }
  4. func Benchmark10Create(b *testing.B) {
  5. benchmarkCreate(b, 10, 1000000)
  6. }
  7. func Benchmark1000Create(b *testing.B) {
  8. benchmarkCreate(b, 1000, 1000000)
  9. }

这里我们写了五个基准测试函数来检测 benchmarkCreate() 函数的性能,它用写缓冲大小变量检测 Create() 函数的性能。

writingBU.gowritingBU_test.go 文件执行 go test 将产生如下输出:

11.9 基准测试的缓冲写入 - 图1

下面的输出也检测了基准测试函数的内存分配:

11.9 基准测试的缓冲写入 - 图2

现在来解释一下这俩个 go tesst 命令的输出。

很明显使用一个大小为 1 个字节的写缓冲是完全无效的并且缓冲所有的操作。另外,这样的缓冲大小需要更多的内存操作,这也使程序运行的更慢!

使用 2 个字节的缓冲可以整个程序速度提升 2 倍,这是好事。然而,这仍然很慢。这同样适用于 4 个字节的写缓冲。

当决定用 10 个字节的写缓冲时,这会变的更快。最后,这个结果显示使用 1,000 字节的写缓冲没有比使用 10 字节的快 100 倍,这意味着在速度和写缓冲大小之间的最佳点是在这俩个值之间。