压测Benchmark基准测试

压测Benchmark基准测试

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