一、什么是基准测试(Benchmark)
在 Go 中,基准测试是通过创建以 Benchmark 开头的函数,并接收一个 *testing.B 类型的参数来实现的。testing.B 提供了控制基准测试执行的接口,比如设置测试执行的次数、记录每次执行的时间等。
每个基准测试函数都必须接受一个 *testing.B 类型的参数。函数体内通过 b.N来控制基准测试的执行次数,Go 会自动调整 b.N 的值,确保每次基准测试运行的时间足够长。
例如:
package main
import (
"testing"
)
// 基准测试函数
func BenchmarkMyFunction(b *testing.B) {
// 这里是测试的代码,每次循环 b.N 次
for i := 0; i < b.N; i++ {
// 被测试的函数或代码块
_ = "hello"
}
}
二、怎么进行基准测试
要运行基准测试,可以使用 go test 命令,指定 -bench 标志来启动基准测试。
go test -bench .
- bench .:表示运行当前目录下所有以 Benchmark 开头的函数。
- bench <pattern> :可以通过提供正则表达式来运行特定的基准测试,比如 go test -bench BenchmarkMyFunction 仅运行 BenchmarkMyFunction。
运行基准测试并报告内存分配
go test -bench . -benchmem
- benchmem:该标志会显示内存分配的详细信息,包括每次基准测试中分配了多少内存。
package main
import (
"testing"
"unsafe"
)
func BenchmarkBytes2Str(b *testing.B) {
aa := []byte("mclink")
for n := 0; n < b.N; n++ {
Bytes2Str(aa)
}
}
func BenchmarkBytes2StrUnsafe(b *testing.B) {
aa := []byte("mclink")
for n := 0; n < b.N; n++ {
Bytes2StrUnsafe(aa)
}
}
func Bytes2Str(b []byte) string {
return string(b)
}
func Bytes2StrUnsafe(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
#从左到右分别表示benchmark函数、运行次数、单次运行消耗的时间、单次运行内存分配的字节数和次数
BenchmarkBytes2Str-8 :基准测试的名称和 CPU 核心数量(这里 -8 表示在 8 核 CPU 上运行)。
317858601:测试函数执行了 20,000,000 次。
3.720 ns/op:每次执行 BenchmarkBytes2Str-8 的平均耗时为 3.72 纳秒。
0 B/op:每次测试没有内存分配。
0 allocs/op:每次测试没有进行内存分配。
三、复杂的基准测试
有时在基准测试中,某些初始化工作并不应该计入测试时间。可以使用 b.StartTimer() 和 b.StopTimer() 来控制计时开始和结束。
func BenchmarkWithStartStopTimer(b *testing.B) {
// 1. 先进行一些初始化工作
setup()
// 2. 在基准测试前停止计时(这部分不会计入基准测试时间)
b.StopTimer()
// 3. 进行某些准备工作或其他代码,这部分不会计入基准测试时间
time.Sleep(100 * time.Millisecond)
// 4. 启动计时,开始计入基准测试时间
b.StartTimer()
// 5. 开始基准测试
for i := 0; i < b.N; i++ {
performWork()
}
// 6. 结束时停止计时
b.StopTimer()
}
- b.StartTimer():开始计时。
- b.StopTimer():停止计时。
- b.ResetTimer():重置计时器,确保计时仅限于你关心的代码
你可以使用 b.ReportAllocs() 启用内存报告,来查看每次基准测试中分配了多少内存。
func BenchmarkAllocations(b *testing.B) {
b.ReportAllocs() // 启用内存分配报告
for i := 0; i < b.N; i++ {
_ = make([]byte, 1024) // 模拟内存分配
}
}
- 通过 -benchtime 设置基准测试时间
go test 命令支持 -benchtime 标志,可以控制基准测试的执行时长。例如,如果你想要让基准测试执行 2 秒钟:
go test -bench . -benchtime=2s
- 跳过普通单元测试,只运行基准测试
如果你只想运行基准测试,而跳过普通的单元测试,可以使用 -run=^$ 来过滤单元测试:
go test -bench . -run=^$
- 和pprof 混合双打
package main
import (
"fmt"
"testing"
"time"
"runtime/pprof"
"os"
)
// 需要进行基准测试的函数
func performTask() {
// 模拟耗时操作
time.Sleep(100 * time.Millisecond)
}
// 基准测试函数
func BenchmarkWithPprof(b *testing.B) {
// 创建一个文件来保存 CPU 分析数据
f, err := os.Create("cpu.pprof")
if err != nil {
b.Fatal("could not create CPU profile: ", err)
}
defer f.Close()
// 启动 pprof,开始记录 CPU 使用情况
if err := pprof.StartCPUProfile(f); err != nil {
b.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
// 进行基准测试
for i := 0; i < b.N; i++ {
performTask()
}
}
// 运行基准测试的命令:go test -bench .
// 运行完之后会生成一个 cpu.pprof 文件,可以用 go tool pprof 查看分析数据。
如何使用 pprof 请看上一篇文章。
下一篇:Go的 Trace 工具~