1.1 Benchmark

Kesa...大约 4 分钟golang

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.Bb.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-1212 表示 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

  1. https://geektutu.com/post/hpg-benchmark.htmlopen in new window
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2