Benchmark 基准测试
简单基测
当我们尝试去优化代码的性能时,首先得知道当前的性能怎么样。
Go 语言标准库内置的 testing 测试框架提供了基准测试(benchmark)的能力,能让我们很容易地对某一段代码进行性能测试。
性能测试受环境的影响很大,为了保证测试的可重复性,在进行性能测试时,尽可能地保持测试环境的稳定。
- 机器处于闲置状态,测试时不要执行其他任务,也不要和其他人共享硬件资源。
- 机器是否关闭了节能模式,一般笔记本会默认打开这个模式,测试时关闭。
- 避免使用虚拟机和云主机进行测试,一般情况下,为了尽可能地提高资源的利用率,虚拟机和云主机 CPU 和内存一般会超分配,超分机器的性能表现会非常地不稳定。
参考:https://geektutu.com/post/hpg-benchmark.html
Benchmark 是如何工作的
单元测试:
- 运行当前 package 内的所有用例:go test 【包名】或 go test .
- 递归执行当前目录下所有用例:go test ./... 或 go test 【目录名】/...
- 运行指定文件单元测试: go test -v [单元测试文件]. // 如 go test -v foo_test.go
- 运行指定单元测试用例:go test -v [单元测试文件] -run [单元测试函数]. //如 go test -v foo_test.go -run TestFoo
- 压测:go test -bench .
执行参数 | 简介 | 备注 |
---|---|---|
-bench=‘Fib$’ | 可传入正则,匹配用例 | 如只运行以 Fib 结尾的 benchmark 用例 |
-cpu=2,4 | 可改变 CPU 核数 | GOMAXPROCS,CPU 核数,默认机器的核数 |
-benchtime=5s -benchtime=50x |
可指定执行时间或具体次数 | benchmark 的默认时间是 1s,决定了 b.N 的次数,测试时间 -benchtime 的值除了是时间外,还可以是具体的次数。 例如,执行 30 次可以用-benchtime=30x |
-count=3 | 可设置 benchmark 轮数 | 参数可以用来设置 benchmark 的轮数,默认是 1 轮 |
-benchmem | 可查看内存分配量和分配次数 | 参数可以度量内存分配的次数,添加此参数后会在结果之后显示 n allocs/op 也就是内存分配了 n 次,m B/op, 总共分配了 m 字节 8003641 B/op == 8 003 641 B=7.63M |
-cpuprofile=./cpu.prof | 生成 CPU 性能分析 | 执行 CPU profiling,并把结果保存在 cpu.prof |
-memprofile=./mem.prof | 生成内存性能分析 | 执行 Mem profiling,并把结果保存在 cpu.prof 文件中 |
import (
"math/rand"
"testing"
"time"
)
func generateWithCap(n int) []int {
rand.Seed(time.Now().UnixNano())
nums := make([]int, 0, n)
for i := 0; i < n; i++ {
nums = append(nums, rand.Int())
}
return nums
}
func generate(n int) []int {
rand.Seed(time.Now().UnixNano())
nums := make([]int, 0)
for i := 0; i < n; i++ {
nums = append(nums, rand.Int())
}
return nums
}
func BenchmarkGenerateWithCap(b *testing.B) {
for n := 0; n < b.N; n++ {
generateWithCap(1000000)
}
}
func BenchmarkGenerate(b *testing.B) {
for n := 0; n < b.N; n++ {
generate(1000000)
}
}
$ go test -bench='Generate' -benchmem -cpu=2,4,8 . // 执行的指令
goos: darwin // 操作系统
goarch: arm64 // cpu处理器
pkg: itxj.com/xj-utils/utils/algorithm // 测试执行所在的包
BenchmarkGenerateWithCap-2 82 13937680 ns/op 8003584 B/op 1 allocs/op
BenchmarkGenerateWithCap-4 84 14083593 ns/op 8003595 B/op 1 allocs/op
BenchmarkGenerateWithCap-8 85 13938689 ns/op 8003616 B/op 1 allocs/op
BenchmarkGenerate-2 68 15586489 ns/op 45188365 B/op 40 allocs/op
BenchmarkGenerate-4 70 15646168 ns/op 45188417 B/op 40 allocs/op
BenchmarkGenerate-8 73 15948826 ns/op 45188464 B/op 41 allocs/op
// 执行测试函数所用CPU核数 执行次数 每次花费时间左移动9位为秒s 每次分配内存字节B左移6位为m 每次分配内存次数
// 如用2核CPU执行BenchmarkGenerate函数 0014083593ns=0.014s 8003584B=7.9m
PASS
ok itxj.com/xj-utils/utils/algorithm 7.682s // 总共执行耗时
结果参数 | 简介 | |
---|---|---|
b.N | benchmark 的次数 b.N,默认是-benchtime=1s 内执行的次数,b.N 从 -benchtime=1s 开始,如果该用例能够在 1s 内完成,b.N 的值大概以 1, 2, 3, 5, 10, 20, 30, 50, 100 这样的序列递增,越到后面,增加得越快 |
|
goos | 操作系统 | |
goarch | CPU 处理器 | |
pkg | 所在的包 | |
BenchmarkGenerate-2 BenchmarkGenerate-4 BenchmarkGenerate-8 |
【测试函数名】-【CPU 核数】BenchmarkGenerate-2, 表示使用 2 核 CPU 执行 BenchmarkGenerate 测试函数 |
|
ns/op | 每次耗时多少毫秒 | |
B/op | 每次分配内存多少字节 Bytes | |
allocs/op | 每次分配多少次内存 |
运行测试用例:
func fib(n int) int {
if n == 0 || n == 1 {
return n
}
return fib(n-2) + fib(n-1)
}
import "testing"
func BenchmarkFib(b *testing.B) {
for n := 0; n < b.N; n++ {
fib(30) // run fib(30) b.N times
}
}
提高准确度
内存分配情况
不同输入复杂度测试
其他注意事项
- ResetTimer
- StopTimer
- StartTimer