基准测试的缓冲写入

在这节,我们将使用 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() 函数写基准测试函数时您将遇到问题,因为您需要使用 BUFFERSIZEFILESIZE 全局变量,它们都是在 writingBU.gomain() 函数中初始化的。

这将是一个难点在 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.gowritingBU_test.go 文件执行 go test 将产生如下输出:

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

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

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

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

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

最后编辑: kuteng  文档更新时间: 2021-03-27 20:14   作者:kuteng