用于测试函数性能
Go 中的基准测试在许多方面类似于单元测试,但有关键的不同之处,并且服务于不同的目的。由于它们不像 Go 中的单元测试那样广为人知,本文旨在介绍 Go 的基准测试:如何创建、如何运行它们、如何读取结果以及一些指向创建基准测试的一些高级主题的指针在去。
基准测试是测试 Go 代码性能的函数,它们包含testing
在标准 Go 库的包中,因此无需任何外部库的依赖即可使用。
执行基准测试时,会向您提供有关执行时间的一些信息,如果需要,还会提供基准测试下代码的内存占用量。
创建基准 #
创建cc_test.go文件
要创建基准测试,您需要在 go 文件中导入testing
包并以创建测试函数的类似方式创建基准测试函数。
例如,在定义单元测试时,我们func TestAny(t *testing)
以开头的形式编写函数,而在定义基准测试时,我们将创建一个**func BenchmarkAny(b \*testing.B)
**.
Go 的基准测试在单元测试方面的一个显着差异是从 0 到b.N
. 事实上,基准测试会运行多次,以确保收集到足够的数据以提高基准测试下代码性能测量的准确性。
该字段b.N
不是固定值,而是动态调整以确保基准测试功能至少运行 1 秒。
这里展示的是基准和测试函数之间的比较:
func Benchmark1Sort(b *testing.B) {
for i := 0; i < b.N; i++ {
sort.Ints(generateSlice(1000))
}
}
func Test1Sort(t *testing.T) {
slice := generateSlice(1000)
if len(slice) != 1000 {
t.Errorf("unexpected slice size: %d", len(slice))
}
}
运行基准 #
运行 Go 的基准测试的起点是go test
命令,在这里我们将看到我们需要确保我们不只是运行单元测试。
基本用法 #
go test -bench .
它本身go test
只运行单元测试,所以我们需要添加标志-bench
来指示 go test 也运行基准测试。
具体来说,此命令运行当前包中的所有单元测试和基准测试,如**.** 添加为-bench
标志的参数。
“ . ”值实际上是一个正则表达式,可以描述将执行哪些基准测试。例如,go test -bench ^Benchmark1Sort$
将运行名为Benchmark1Sort的基准测试。
与运行单元测试时一样,您可以-v
为verbose添加标志,这将显示有关执行的基准测试以及任何打印输出、日志、fmt.Prints 等的更多详细信息,或添加路径(如“./. ..") 来查找特定包(或所有包和子包)的基准。
go test -bench . -v
go test -bench . ./...
仅运行基准测试 #
要从 的执行中过滤掉所有单元测试,应该使用go test
该-run ^$
标志。
go test -run ^$ -bench .
该标志-run
本身用于指定应该运行哪些单元测试。它的参数是一个正则表达式。当我们使用^$
as 参数时,我们有效地过滤掉了所有测试,这意味着只会执行当前包中存在的基准测试。
多次运行 #
只需添加-count
参数即可运行您的基准测试指定的次数:所有执行的结果将显示在输出中。
$ go test -bench ^Benchmark1Sort$ -run ^$ -count 4
goos: linux
goarch: amd64
Benchmark1Sort-12 10207 134834 ns/op
Benchmark1Sort-12 7554 175572 ns/op
Benchmark1Sort-12 7904 148960 ns/op
Benchmark1Sort-12 8568 147594 ns/op
PASS
ok _/home/mcaci/code/github.com/mcaci/dev-art/go-bench 7.339s
在对多次运行的结果进行采样以对基准数据进行统计分析时,此标志很有用。
读取基准测试结果 #
让我们再次使用以下示例并运行它go test -bench
以检查其输出。
func Benchmark1Sort(b *testing.B) {
for i := 0; i < b.N; i++ {
sort.Ints(generateSlice(1000))
}
}
有执行时间 #
对于第一个分析,我们运行基准测试 go test -bench ^Benchmark1Sort$ -run ^$
$ go test -bench ^Benchmark1Sort$ -run ^$
goos: linux
goarch: amd64
Benchmark1Sort-12 9252 110547 ns/op
PASS
ok _/home/mcaci/code/github.com/mcaci/dev-art/go-bench 1.053s
显示的输出存在于任何基准执行中,它显示:
-
Go运行的
环境
信息,也是通过运行获取的
go env GOOS GOARCH
(区分大小写)
- 在我们的示例中,它们是goos: linux和goarch: amd64。
-
该
基准行
组成:
- 该基准运行的名称,Benchmark1Sort-12,即本身构成的函数名,的Benchmark1Sort用于基准测试程序运行,随后CPU的数量,12。
- 该次数的循环已经执行,9252。
- 被测试函数的平均运行时间,以每次操作的纳秒表示,
sort.Ints(generateSlice(1000))
在本例中为110547 ns/op。
-
有关基准总体状态、基准下的包和执行总时间的信息。
关于 CPU 数量的快速说明:可以使用-cpu
标志指定此参数;基准测试将在标志中定义的每个 CPU 运行多次。
$ go test -bench ^Benchmark1Sort$ -run ^$ -cpu 1,2,4
goos: linux
goarch: amd64
Benchmark1Sort 9280 113086 ns/op
Benchmark1Sort-2 9379 117156 ns/op
Benchmark1Sort-4 8637 118818 ns/op
PASS
ok _/home/mcaci/code/github.com/mcaci/dev-art/go-bench 3.234s
如果省略此标志,则从 Go 变量GOMAXPROCS 中获取默认值,并且 CPU 的数量在等于 1 时不会打印在输出中。
带执行时间和内存 #
要在输出中添加有关内存占用的信息,您可以添加-benchmem
如下标志。
$ go test -bench ^Benchmark1Sort$ -run ^$ -benchmem
goos: linux
goarch: amd64
Benchmark1Sort-12 10327 116903 ns/op 8224 B/op 2 allocs/op
PASS
ok _/home/mcaci/code/github.com/mcaci/dev-art/go-bench 2.128s
在基准行的输出中添加了两个新列:
- 的字节数所要求的操作下基准,8224 B /运算
- 的分配数由下基准,则操作进行2个分配/运
编写更复杂的基准测试 #
以下是如何编写更复杂的基准测试的一些示例。
启动定时器/停止定时器/复位定时器 #
当有需要实际测量花费执行代码的基准,实际使用情况的时间之前做一些设置StartTimer
,StopTimer
并ResetTimer
有助于实际需要由基准工具加以考虑的码位隔离。
让我们以前面的代码片段为例,将切片的创建与排序操作隔离开来,只测量后者的执行情况。
为此,我们可以写:
func Benchmark2aSort(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
s := generateSlice(1000)
b.StartTimer()
sort.Ints(s)
}
}
通过使用b.StopTimer()
我们发出信号,从此时开始执行将不会成为基准测试的一部分,直到b.StartTimer()
被调用,这意味着在每个循环中,我们只考虑sort.Ints(s)
在基准测试执行期间收集的数据。
如果我们想在开始时准备切片并使其成为基准测试的不变量,我们可以改写:
func Benchmark2bSort(b *testing.B) {
s := generateSlice(1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
sort.Ints(s)
}
}
通过使用b.ResetTimer()
我们丢弃到目前为止收集的所有数据并重新开始收集数据以进行基准测试,从而有效地忽略generateSlice
了整体结果中调用的执行时间。
基准测试用例和子基准测试 #
与测试一样,基准测试也可以从测试用例和执行循环的结构中受益,以创建子基准测试。
让我们看一个例子:
func Benchmark3Sort(b *testing.B) {
benchData := map[string]struct {
size int
}{
"with size 1000": {size: 1000},
"with size 10000": {size: 10000},
"with size 100000": {size: 100000},
"with size 1000000": {size: 1000000},
}
b.ResetTimer()
for benchName, data := range benchData {
b.StopTimer()
s := generateSlice(data.size)
b.StartTimer()
b.Run(benchName, func(b *testing.B) {
for i := 0; i < b.N; i++ {
sort.Ints(s)
}
})
}
}
在这个例子中,我们使用 amap[string]struct{...}
来定义我们的基准测试用例和数据,就像我们对测试用例的复杂测试所做的一样,我们调用b.Run(name string, f func(*testing.B))
来创建单独执行我们的基准测试的子基准测试。
$ go test -bench ^Benchmark3Sort$ -run ^$
goos: linux
goarch: amd64
Benchmark3Sort/with_size_1000000-12 10 130396565 ns/op
Benchmark3Sort/with_size_1000-12 23210 58078 ns/op
Benchmark3Sort/with_size_10000-12 1300 865703 ns/op
Benchmark3Sort/with_size_100000-12 118 8718656 ns/op
PASS
ok _/home/mcaci/code/github.com/mcaci/dev-art/go-bench 6.670s
请注意,作为benchmark_name/benchmark_case_name-number-of-cpus的基准操作输出的一部分,基准案例的名称附加到基准名称。