1.1 Benchmark
...大约 4 分钟
1. Benchmark 的使用
1.1 示例:Fibonacci 数列
// fib.go
func fib(n int) int {
if n == 0 || n == 1 {
return 1
}
return fib(n-1) + fib(n-2)
}
在fib_test.go
中创建 benchmark 测试用例:
// fib_test.go
import "testing"
func BenchmarkFib(b *testing.B) {
for i := 0; i < b.N; i++ {
fib(30)
}
}
- Benchmark 测试和单元测试都是放在
xxx_text.go
文件中 - 测试函数名要以
Benchmark
开头,单元测试以Test
开头 - 测试函数参数为
b *testing.B
,单元测试函数参数为t *testing.T
1.2 运行 Benchmark
运行 benchmark 测试,需要使用:
$ go test -bench .
-bench
:表示开启 benchmark.
:正则表达式,用于执行匹配的 benchmark 函数名,此处为任意函数
goos: windows
goarch: amd64
pkg: high-performance-go/01-performance-analysis
...
BenchmarkFib
BenchmarkFib-12 280 4173083 ns/op
PASS
1.3 Benchmark 如何工作
对于b *testing.B
,b.N
表示测试用例的运行次数。
b.N
从 1 开始,若用例能够在 1s 内完成,b.N
便会增加并再次执行。b.N
大致以 1, 2, 3, 5, 10, 20, 30, 50, 100...
的形式递增。
BenchmarkFib-12 280 4173083 ns/op
其中的 BenchmarkFib-12
的 12
表示 CPU 的核心数,可以通过-cpu
来修改 GOMAXPRROCS
的值,并且可以传入列表作为参数:
$ go test -bench . -cpu 2,4
BenchmarkFib
BenchmarkFib-2 285 4176162 ns/op
BenchmarkFib-4 294 4187019 ns/op
PASS
ok high-performance-go/01-performance-analysis 3.305s
-cpu 2,4
:表示分别使用 2 个和 4 个核心进行测试,测试的函数是串行的多核心对结果没有影响285
:表示测试用例在测试时间3.305s
内执行的次数4176162 ns/op
:表示每次执行的时间
1.4 提升准确度
对于性能测试来说,提升准确度的一个手段是增加测试次数。
使用-benchtime
设定测试时间,其默认时间为 1s。
$ go test -bench . -benchtime 5s
BenchmarkFib-12 1448 4116528 ns/op
PASS
ok high-performance-go/01-performance-analysis 6.425s
6.5s
:包含了编译,执行,销毁等时间,比实际测试时间长4116528
:可以看出,每次执行时间没有大的变化
-benchtime
可以设定具体次数:
$ go test -bench . -benchtime 30x
BenchmarkFib-12 30 4228023 ns/op
PASS
ok high-performance-go/01-performance-analysis 0.182s
-benchtime 30x
:表示只执行 30 次
-count
用于设置测试轮数:
$ go test -bench . -count 3
BenchmarkFib-12 285 4268897 ns/op
BenchmarkFib-12 285 4190173 ns/op
BenchmarkFib-12 288 4254332 ns/op
PASS
ok high-performance-go/01-performance-analysis 5.347s
-count 3
:表示测试 3 轮
1.5 内存分配情况
-benchmem
可以查看内存分配的次数。
下例中,分别使用两种不同的方式生成 slice:
func genSlice(n int) []int {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
a := make([]int, 0)
for i := 0; i < n; i++ {
a = append(a, r.Int())
}
return a
}
func genSliceWithCap(n int) []int {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
a := make([]int, 0, n)
for i := 0; i < n; i++ {
a = append(a, r.Int())
}
return a
}
const GenSliceLen = 500000
func BenchmarkGenSlice(b *testing.B) {
for i := 0; i < b.N; i++ {
genSlice(GenSliceLen)
}
}
func BenchmarkGenSliceWithCap(b *testing.B) {
for i := 0; i < b.N; i++ {
genSliceWithCap(GenSliceLen)
}
}
$ go test -bench 'GenSlice' -benchmem
BenchmarkGenSlice-12 181 6479400 ns/op 21088848 B/op 36 allocs/op
BenchmarkGenSliceWithCap-12 507 2526058 ns/op 4011269 B/op 2 allocs/op
PASS
ok high-performance-go/01-performance-analysis 3.819s
1.6 测试不同的输入
不同的函数时间复杂度不同,可以使用 benchmark 来验证时间和空间复杂度。
func benchGenSliceHelper(n int, b *testing.B) {
for i := 0; i < b.N; i++ {
genSlice(n)
}
}
func BenchmarkGenSlice1000(b *testing.B) { benchGenSliceHelper(1000, b) }
func BenchmarkGenSlice10000(b *testing.B) { benchGenSliceHelper(10000, b) }
func BenchmarkGenSlice100000(b *testing.B) { benchGenSliceHelper(100000, b) }
$ go test -bench 'GenSlice1' -benchmem
BenchmarkGenSlice1000-12 61258 19013 ns/op 30584 B/op 13 allocs/op
BenchmarkGenSlice10000-12 9230 124317 ns/op 363001 B/op 20 allocs/op
BenchmarkGenSlice100000-12 877 1234429 ns/op 4106772 B/op 29 allocs/op
可以看出其空间和时间复杂度为 O(n)
2. 注意事项
2.1 ResetTimer
如果在 benchmark 开始前,需要一些准备工作,如果准备工作比较耗时,则需要将这部分代码的耗时忽略掉。比如下面的例子:
func BenchmarkFib(b *testing.B) {
time.Sleep(time.Second * 3) // 模拟耗时准备任务
for n := 0; n < b.N; n++ {
fib(30) // run fib(30) b.N times
}
}
// bench
BenchmarkFib-8 50 65912552 ns/op
PASS
ok example 6.319s
此时测试的时间收到干扰,需要b.ResetTimer()
重置计时,排除干扰。
func BenchmarkFib(b *testing.B) {
time.Sleep(time.Second * 3) // 模拟耗时准备任务
b.ResetTimer() // 重置定时器
for n := 0; n < b.N; n++ {
fib(30) // run fib(30) b.N times
}
}
// bench
BenchmarkFib-8 50 6187485 ns/op
PASS
ok example 6.330s
2.2 StopTimer & StartTimer
若函数调用前后需要一些准备工作和清理工作,可以使用 StopTimer
暂停计时以及使用 StartTimer
开始计时。
例如测试排序数组,需要在排序之前生成数组。
func bubbleSort(nums []int) {
for i := 0; i < len(nums); i++ {
for j := 1; j < len(nums)-i; j++ {
if nums[j] < nums[j-1] {
nums[j], nums[j-1] = nums[j-1], nums[j]
}
}
}
}
// _test
func BenchmarkBubbleSort(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
nums := genSliceWithCap(50000)
b.StartTimer()
bubbleSort(nums)
}
}
// bench
BenchmarkBubbleSort-12 1 2949370800 ns/op 8 B/op 1 allocs/op
PASS
ok high-performance-go/01-performance-analysis 3.381s
Reference
Powered by Waline v2.15.2