# testing - 基准测试 #
在 _test.go 结尾的测试文件中,如下形式的函数:
func BenchmarkXxx(*testing.B)
被认为是基准测试,通过 "go test" 命令,加上 `-bench` flag 来执行。多个基准测试按照顺序运行。
基准测试函数样例看起来如下所示:
```go
func BenchmarkHello(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Sprintf("hello")
}
}
```
基准函数会运行目标代码 b.N 次。在基准执行期间,会调整 b.N 直到基准测试函数持续足够长的时间。输出
BenchmarkHello 10000000 282 ns/op
意味着循环执行了 10000000 次,每次循环花费 282 纳秒(ns)。
如果在运行前基准测试需要一些耗时的配置,则可以先重置定时器:
```go
func BenchmarkBigLen(b *testing.B) {
big := NewBig()
b.ResetTimer()
for i := 0; i < b.N; i++ {
big.Len()
}
}
```
如果基准测试需要在并行设置中测试性能,则可以使用 RunParallel 辅助函数; 这样的基准测试一般与 `go test -cpu` 标志一起使用:
```go
func BenchmarkTemplateParallel(b *testing.B) {
templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
b.RunParallel(func(pb *testing.PB) {
// 每个 goroutine 有属于自己的 bytes.Buffer.
var buf bytes.Buffer
for pb.Next() {
// 所有 goroutine 一起,循环一共执行 b.N 次
buf.Reset()
templ.Execute(&buf, "World")
}
})
}
```
## 基准测试示例 ##
接着上一节的例子,我们对 `Fib` 进行基准测试:
```go
func BenchmarkFib10(b *testing.B) {
for n := 0; n < b.N; n++ {
Fib(10)
}
}
```
执行 `go test -bench=.`,输出:
```
$ go test -bench=.
BenchmarkFib10-4 3000000 424 ns/op
PASS
ok chapter09/testing 1.724s
```
这里测试了 `Fib(10)` 的情况,我们可能需要测试更多不同的情况,这时可以改写我们的测试代码:
```go
func BenchmarkFib1(b *testing.B) { benchmarkFib(1, b) }
func BenchmarkFib2(b *testing.B) { benchmarkFib(2, b) }
func BenchmarkFib3(b *testing.B) { benchmarkFib(3, b) }
func BenchmarkFib10(b *testing.B) { benchmarkFib(10, b) }
func BenchmarkFib20(b *testing.B) { benchmarkFib(20, b) }
func BenchmarkFib40(b *testing.B) { benchmarkFib(40, b) }
func benchmarkFib(i int, b *testing.B) {
for n := 0; n < b.N; n++ {
Fib(i)
}
}
```
再次执行 `go test -bench=.`,输出:
```
$ go test -bench=.
BenchmarkFib1-4 1000000000 2.58 ns/op
BenchmarkFib2-4 200000000 7.38 ns/op
BenchmarkFib3-4 100000000 13.0 ns/op
BenchmarkFib10-4 3000000 429 ns/op
BenchmarkFib20-4 30000 54335 ns/op
BenchmarkFib40-4 2 805759850 ns/op
PASS
ok chapter09/testing 15.361s
```
默认情况下,每个基准测试最少运行 1 秒。如果基准测试函数返回时,还不到 1 秒钟,`b.N` 的值会按照序列 1,2,5,10,20,50,... 增加,同时再次运行基准测测试函数。
我们注意到 `BenchmarkFib40` 一共才运行 2 次。为了更精确的结果,我们可以通过增加标志 `-benchtime` 来它运行更多次。
```
$ go test -bench=Fib40 -benchtime=20s
BenchmarkFib40-4 30 838675800 ns/op
```
## B 类型 ##
B 是传递给基准测试函数的一种类型,它用于管理基准测试的计时行为,并指示应该迭代地运行测试多少次。
一个基准测试在它的基准测试函数返回时,又或者在它的基准测试函数调用 `FailNow`、`Fatal`、`Fatalf`、`SkipNow`、`Skip` 或者 `Skipf` 中的任意一个方法时,测试即宣告结束。至于其他报告方法,比如 `Log` 和 `Error` 的变种,则可以在其他 goroutine 中同时进行调用。
跟单元测试一样,基准测试会在执行的过程中积累日志,并在测试完毕时将日志转储到标准错误。但跟单元测试不一样的是,为了避免基准测试的结果受到日志打印操作的影响,基准测试总是会把日志打印出来。
B 类型中的报告方法使用方式和 T 类型是一样的,一般来说,基准测试中也不需要使用,毕竟主要是测性能。这里我们对 B 类型中其他的一些方法进行讲解。
### 计时方法 ###
有三个方法用于计时:
1. StartTimer:开始对测试进行计时。该方法会在基准测试开始时自动被调用,我们也可以在调用 StopTimer 之后恢复计时;
2. StopTimer:停止对测试进行计时。当你需要执行一些复杂的初始化操作,并且你不想对这些操作进行测量时,就可以使用这个方法来暂时地停止计时。
3. ResetTimer:对已经逝去的基准测试时间以及内存分配计数器进行清零。对于正在运行中的计时器,这个方法不会产生任何效果。本节开头有使用示例。
### 并行执行 ###
通过 `RunParallel` 方法以并行的方式执行给定的基准测试。RunParallel 会创建出多个 goroutine,并将 b.N 分配给这些 goroutine 执行,其中 goroutine 数量的默认值为 GOMAXPROCS。用户如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性,那么可以在 RunParallel 之前调用 SetParallelism(如 SetParallelism(2),则 goroutine 数量为 2*GOMAXPROCS)。RunParallel 通常会与 -cpu 标志一同使用。
`body` 函数将在每个 goroutine 中执行,这个函数需要设置所有 goroutine 本地的状态,并迭代直到 `pb.Next` 返回 false 值为止。因为 `StartTimer`、`StopTime` 和 `ResetTimer` 这三个方法都带有全局作用,所以 body 函数不应该调用这些方法; 除此之外,body 函数也不应该调用 Run 方法。
具体的使用示例,在本节开头已经提供!
### 内存统计 ###
`ReportAllocs` 方法用于打开当前基准测试的内存统计功能, 与 `go test` 使用 `-benchmem` 标志类似,但 `ReportAllocs` 只影响那些调用了该函数的基准测试。
测试示例:
```go
func BenchmarkTmplExucte(b *testing.B) {
b.ReportAllocs()
templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
b.RunParallel(func(pb *testing.PB) {
// Each goroutine has its own bytes.Buffer.
var buf bytes.Buffer
for pb.Next() {
// The loop body is executed b.N times total across all goroutines.
buf.Reset()
templ.Execute(&buf, "World")
}
})
}
```
测试结果类似这样:
BenchmarkTmplExucte-4 2000000 898 ns/op 368 B/op 9 allocs/op
### 基准测试结果 ##
对
BenchmarkTmplExucte-4 2000000 898 ns/op 368 B/op 9 allocs/op
中的每一项,你是否都清楚是什么意思呢?
`testing` 包中的 `BenchmarkResult` 类型能为你提供帮助。它保存了基准测试的结果。
它的定义如下:
```go
type BenchmarkResult struct {
N int // The number of iterations. 即 b.N
T time.Duration // The total time taken. 基准测试花费的时间
Bytes int64 // Bytes processed in one iteration. 一次迭代处理的字节数,通过 b.SetBytes 设置
MemAllocs uint64 // The total number of memory allocations. 总的分配内存的次数
MemBytes uint64 // The total number of bytes allocated. 总的分配内存的字节数
}
```
该类型还提供了相应的计算每个操作每秒相应指标的方法。示例如下:
```go
package main
import (
"bytes"
"fmt"
"testing"
"text/template"
)
func main() {
benchmarkResult := testing.Benchmark(func(b *testing.B) {
templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
// RunParallel will create GOMAXPROCS goroutines
// and distribute work among them.
b.RunParallel(func(pb *testing.PB) {
// Each goroutine has its own bytes.Buffer.
var buf bytes.Buffer
for pb.Next() {
// The loop body is executed b.N times total across all goroutines.
buf.Reset()
templ.Execute(&buf, "World")
}
})
})
// fmt.Printf("%8d\t%10d ns/op\t%10d B/op\t%10d allocs/op\n", benchmarkResult.N, benchmarkResult.NsPerOp(), benchmarkResult.AllocedBytesPerOp(), benchmarkResult.AllocsPerOp())
fmt.Printf("%s\t%s\n", benchmarkResult.String(), benchmarkResult.MemString())
}
```
# 导航 #
- 上一节:[testing - 单元测试](09.1.md)
- 下一节:[testing - 子测试](09.3.md)
- 简介
- 第一章 输入输出 (Input/Output)
- 1.1 io — 基本的 IO 接口
- 1.2 ioutil — 方便的 IO 操作函数集
- 1.3 fmt — 格式化 IO
- 1.4 bufio — 缓存 IO
- 第二章 文本
- 2.1 strings — 字符串操作
- 2.2 bytes — byte slice 便利操作
- 2.3 strconv — 字符串和基本数据类型之间转换
- 2.4 regexp — 正则表达式
- 2.5 unicode — Unicode 码点、UTF-8/16 编码
- 第三章 数据结构与算法
- 3.1 sort — 排序算法
- 3.2 index/suffixarray — 后缀数组实现子字符串查询
- 3.3 container — 容器数据类型:heap、list 和 ring
- 第四章 日期与时间
- 4.1 主要类型概述
- 4.2 时区
- 4.3 Time类型详解
- 4.4 定时器
- 第六章 文件系统
- 6.1 os — 平台无关的操作系统功能实现
- 6.2 path/filepath — 操作路径
- 第七章 数据持久存储与交换
- 7.1 database/sql — SQL/SQL-Like 数据库操作接口
- 第八章 数据压缩与归档
- 8.1 flate * DEFLATE 压缩算法
- 第九章 测试
- 9.1 testing * 单元测试
- 9.2 testing * 基准测试
- 9.3 testing * 子测试
- 9.4 testing * 运行并验证示例
- 9.5 testing * 其他功能
- 9.6 httptest * HTTP 测试辅助工具
- 9.7 总结
- 第十章 进程、线程与 goroutine
- 10.1 创建进程
- 10.2 进程属性和控制
- 10.3 线程
- 10.4 进程间通信
- 第十三章 应用构建 与 debug
- 13.1 flag * 命令行参数解析
- 13.2 log * 日志记录
- 13.3 expvar * 公共变量的标准化接口
- 13.4 runtime/debug * 运行时的调试工具