summaryrefslogtreecommitdiff
path: root/src/pkg/testing/benchmark.go
blob: b552a132015820bd6baa416225ff0e1a1a1f12cc (plain)
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package testing

import (
	"flag";
	"fmt";
	"os";
	"time";
)

var matchBenchmarks = flag.String("benchmarks", "", "regular expression to select benchmarks to run")

// An internal type but exported because it is cross-package; part of the implementation
// of gotest.
type Benchmark struct {
	Name	string;
	F	func(b *B);
}

// B is a type passed to Benchmark functions to manage benchmark
// timing and to specify the number of iterations to run.
type B struct {
	N		int;
	benchmark	Benchmark;
	ns		int64;
	bytes		int64;
	start		int64;
}

// StartTimer starts timing a test.  This function is called automatically
// before a benchmark starts, but it can also used to resume timing after
// a call to StopTimer.
func (b *B) StartTimer()	{ b.start = time.Nanoseconds() }

// StopTimer stops timing a test.  This can be used to pause the timer
// while performing complex initialization that you don't
// want to measure.
func (b *B) StopTimer() {
	if b.start > 0 {
		b.ns += time.Nanoseconds() - b.start
	}
	b.start = 0;
}

// ResetTimer stops the timer and sets the elapsed benchmark time to zero.
func (b *B) ResetTimer() {
	b.start = 0;
	b.ns = 0;
}

// SetBytes records the number of bytes processed in a single operation.
// If this is called, the benchmark will report ns/op and MB/s.
func (b *B) SetBytes(n int64)	{ b.bytes = n }

func (b *B) nsPerOp() int64 {
	if b.N <= 0 {
		return 0
	}
	return b.ns / int64(b.N);
}

// runN runs a single benchmark for the specified number of iterations.
func (b *B) runN(n int) {
	b.N = n;
	b.ResetTimer();
	b.StartTimer();
	b.benchmark.F(b);
	b.StopTimer();
}

func min(x, y int) int {
	if x > y {
		return y
	}
	return x;
}

// roundDown10 rounds a number down to the nearest power of 10.
func roundDown10(n int) int {
	var tens = 0;
	// tens = floor(log_10(n))
	for n > 10 {
		n = n / 10;
		tens++;
	}
	// result = 10^tens
	result := 1;
	for i := 0; i < tens; i++ {
		result *= 10
	}
	return result;
}

// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
func roundUp(n int) int {
	base := roundDown10(n);
	if n < (2 * base) {
		return 2 * base
	}
	if n < (5 * base) {
		return 5 * base
	}
	return 10 * base;
}

// run times the benchmark function.  It gradually increases the number
// of benchmark iterations until the benchmark runs for a second in order
// to get a reasonable measurement.  It prints timing information in this form
//		testing.BenchmarkHello	100000		19 ns/op
func (b *B) run() {
	// Run the benchmark for a single iteration in case it's expensive.
	n := 1;
	b.runN(n);
	// Run the benchmark for at least a second.
	for b.ns < 1e9 && n < 1e9 {
		last := n;
		// Predict iterations/sec.
		if b.nsPerOp() == 0 {
			n = 1e9
		} else {
			n = 1e9 / int(b.nsPerOp())
		}
		// Run more iterations than we think we'll need for a second (1.5x).
		// Don't grow too fast in case we had timing errors previously.
		n = min(int(1.5*float(n)), 100*last);
		// Round up to something easy to read.
		n = roundUp(n);
		b.runN(n);
	}
	ns := b.nsPerOp();
	mb := "";
	if ns > 0 && b.bytes > 0 {
		mb = fmt.Sprintf("\t%7.2f MB/s", (float64(b.bytes)/1e6)/(float64(ns)/1e9))
	}
	fmt.Printf("%s\t%8d\t%10d ns/op%s\n", b.benchmark.Name, b.N, b.nsPerOp(), mb);
}

// An internal function but exported because it is cross-package; part of the implementation
// of gotest.
func RunBenchmarks(benchmarks []Benchmark) {
	// If no flag was specified, don't run benchmarks.
	if len(*matchBenchmarks) == 0 {
		return
	}
	re, err := CompileRegexp(*matchBenchmarks);
	if err != "" {
		println("invalid regexp for -benchmarks:", err);
		os.Exit(1);
	}
	for _, Benchmark := range benchmarks {
		if !re.MatchString(Benchmark.Name) {
			continue
		}
		b := &B{benchmark: Benchmark};
		b.run();
	}
}