前言

go语言本身提供了基准测试的支持,基准测试有以下要求

  • 文件以_test.go结尾
  • 基准测试函数以Benchmark开头
  • 函数参数为*testing.B 类型,无返回值
  • 测试代码放到for循环中 b.N 是基准测试框架提供的,表示循环次数,此值由框架多次执行被测函数后确定。

误解

以前一直没有仔细思考benchmark底层具体的实现,想当然的认为是测试框架会统计每次循环耗时,观察是否趋于稳定,待稳定后,得到一个N值。这种想法其实很幼稚,稍微一思考就不太能实现。首先,for循环是在具体的测试函数中,前后有没有任何可供框架插入勾子函数的机会,统计单次循环耗时其实就不可行;其次,代码在for循环中执行b.N在编译执行后肯定是一个具体的值,不具有动态变更的能力,因此只可能是框架重复执行测试函数,b.N的取值按照某个初始值后阶梯式增长,然后统计耗时

验证

代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
func Op1() {
	for cnt := 0; cnt < 1000; cnt++ {
	}
}
func Op2() {
	for cnt := 0; cnt < 1000000; cnt++ {
	}
}

//BenchmarkOp1-12    	 4269636	       276 ns/op
func BenchmarkOp1(b *testing.B) {
	for cnt := 0; cnt < b.N; cnt++ {
		Op1()
	}
	fmt.Printf("N %v\n", b.N)
}

//BenchmarkOp2-12    	    4518	    254495 ns/op
func BenchmarkOp2(b *testing.B) {
	for cnt := 0; cnt < b.N; cnt++ {
		Op2()
	}
	fmt.Printf("N %v\n", b.N)
}

//BenchmarkOP3-12    	       1	10004286824 ns/op
func BenchmarkOP3(b *testing.B) {
	for cnt := 0; cnt < b.N; cnt++ {
		Op1()
	}
	time.Sleep(10 * time.Second)
}

//BenchmarkOP4-12    	       1	1001792637 ns/op
func BenchmarkOP4(b *testing.B) {
	for cnt := 0; cnt < b.N; cnt++ {
		Op1()
	}
	time.Sleep(1 * time.Second)
}

//BenchmarkGoTakeOP1-12    	 4213207	       279 ns/op
func BenchmarkGoTakeOP1(b *testing.B) {
	var wg sync.WaitGroup
	for cnt := 0; cnt < b.N; cnt++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			Op1()
		}(cnt)
	}
	wg.Wait()
}

//BenchmarkGoTakeOP2-12    	   25840	     49985 ns/op
func BenchmarkGoTakeOP2(b *testing.B) {
	var wg sync.WaitGroup
	for cnt := 0; cnt < b.N; cnt++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			Op2()
		}(cnt)
	}
	wg.Wait()
}

首先分别有两个操作函数op1和op2,benchmark的输出结果字段含义:

观察BenchmarkOp1的输出,如下:

1
2
3
4
5
6
7
BenchmarkOp1
N 1
N 100
N 10000
N 1000000
N 4109054
BenchmarkOp1-12    	 4109054	       267 ns/op

可以知道N的取值是多次阶梯递增后取得的。此处其实还说明了一个信息,基准测试框架并不会去关注单次循环是否趋于稳定,基准测试框架只是忠诚的评估了在测试时间(默认是1s)内大约能执行多少次循环,换句话说,b.N的值是该测试函数在1s或者超过1s时,总共执行的次数。如果单次循环执行耗时,那么将只会执行一次(观察BenchmarkOP3的测试结果可知)