1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package testing
6
7import (
8	"flag"
9	"fmt"
10	"os"
11	"runtime"
12	"sync"
13	"time"
14)
15
16var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run")
17var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark")
18var benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks")
19
20// Global lock to ensure only one benchmark runs at a time.
21var benchmarkLock sync.Mutex
22
23// Used for every benchmark for measuring memory.
24var memStats runtime.MemStats
25
26// An internal type but exported because it is cross-package; part of the implementation
27// of the "go test" command.
28type InternalBenchmark struct {
29	Name string
30	F    func(b *B)
31}
32
33// B is a type passed to Benchmark functions to manage benchmark
34// timing and to specify the number of iterations to run.
35type B struct {
36	common
37	N               int
38	benchmark       InternalBenchmark
39	bytes           int64
40	timerOn         bool
41	showAllocResult bool
42	result          BenchmarkResult
43	// The initial states of memStats.Mallocs and memStats.TotalAlloc.
44	startAllocs uint64
45	startBytes  uint64
46	// The net total of this test after being run.
47	netAllocs uint64
48	netBytes  uint64
49}
50
51// StartTimer starts timing a test.  This function is called automatically
52// before a benchmark starts, but it can also used to resume timing after
53// a call to StopTimer.
54func (b *B) StartTimer() {
55	if !b.timerOn {
56		runtime.ReadMemStats(&memStats)
57		b.startAllocs = memStats.Mallocs
58		b.startBytes = memStats.TotalAlloc
59		b.start = time.Now()
60		b.timerOn = true
61	}
62}
63
64// StopTimer stops timing a test.  This can be used to pause the timer
65// while performing complex initialization that you don't
66// want to measure.
67func (b *B) StopTimer() {
68	if b.timerOn {
69		b.duration += time.Now().Sub(b.start)
70		runtime.ReadMemStats(&memStats)
71		b.netAllocs += memStats.Mallocs - b.startAllocs
72		b.netBytes += memStats.TotalAlloc - b.startBytes
73		b.timerOn = false
74	}
75}
76
77// ResetTimer sets the elapsed benchmark time to zero.
78// It does not affect whether the timer is running.
79func (b *B) ResetTimer() {
80	if b.timerOn {
81		runtime.ReadMemStats(&memStats)
82		b.startAllocs = memStats.Mallocs
83		b.startBytes = memStats.TotalAlloc
84		b.start = time.Now()
85	}
86	b.duration = 0
87	b.netAllocs = 0
88	b.netBytes = 0
89}
90
91// SetBytes records the number of bytes processed in a single operation.
92// If this is called, the benchmark will report ns/op and MB/s.
93func (b *B) SetBytes(n int64) { b.bytes = n }
94
95// ReportAllocs enables malloc statistics for this benchmark.
96// It is equivalent to setting -test.benchmem, but it only affects the
97// benchmark function that calls ReportAllocs.
98func (b *B) ReportAllocs() {
99	b.showAllocResult = true
100}
101
102func (b *B) nsPerOp() int64 {
103	if b.N <= 0 {
104		return 0
105	}
106	return b.duration.Nanoseconds() / int64(b.N)
107}
108
109// runN runs a single benchmark for the specified number of iterations.
110func (b *B) runN(n int) {
111	benchmarkLock.Lock()
112	defer benchmarkLock.Unlock()
113	// Try to get a comparable environment for each run
114	// by clearing garbage from previous runs.
115	runtime.GC()
116	b.N = n
117	b.ResetTimer()
118	b.StartTimer()
119	b.benchmark.F(b)
120	b.StopTimer()
121}
122
123func min(x, y int) int {
124	if x > y {
125		return y
126	}
127	return x
128}
129
130func max(x, y int) int {
131	if x < y {
132		return y
133	}
134	return x
135}
136
137// roundDown10 rounds a number down to the nearest power of 10.
138func roundDown10(n int) int {
139	var tens = 0
140	// tens = floor(log_10(n))
141	for n >= 10 {
142		n = n / 10
143		tens++
144	}
145	// result = 10^tens
146	result := 1
147	for i := 0; i < tens; i++ {
148		result *= 10
149	}
150	return result
151}
152
153// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
154func roundUp(n int) int {
155	base := roundDown10(n)
156	switch {
157	case n <= base:
158		return base
159	case n <= (2 * base):
160		return 2 * base
161	case n <= (5 * base):
162		return 5 * base
163	default:
164		return 10 * base
165	}
166}
167
168// run times the benchmark function in a separate goroutine.
169func (b *B) run() BenchmarkResult {
170	go b.launch()
171	<-b.signal
172	return b.result
173}
174
175// launch launches the benchmark function.  It gradually increases the number
176// of benchmark iterations until the benchmark runs for a second in order
177// to get a reasonable measurement.  It prints timing information in this form
178//		testing.BenchmarkHello	100000		19 ns/op
179// launch is run by the fun function as a separate goroutine.
180func (b *B) launch() {
181	// Run the benchmark for a single iteration in case it's expensive.
182	n := 1
183
184	// Signal that we're done whether we return normally
185	// or by FailNow's runtime.Goexit.
186	defer func() {
187		b.signal <- b
188	}()
189
190	b.runN(n)
191	// Run the benchmark for at least the specified amount of time.
192	d := *benchTime
193	for !b.failed && b.duration < d && n < 1e9 {
194		last := n
195		// Predict iterations/sec.
196		if b.nsPerOp() == 0 {
197			n = 1e9
198		} else {
199			n = int(d.Nanoseconds() / b.nsPerOp())
200		}
201		// Run more iterations than we think we'll need for a second (1.5x).
202		// Don't grow too fast in case we had timing errors previously.
203		// Be sure to run at least one more than last time.
204		n = max(min(n+n/2, 100*last), last+1)
205		// Round up to something easy to read.
206		n = roundUp(n)
207		b.runN(n)
208	}
209	b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes}
210}
211
212// The results of a benchmark run.
213type BenchmarkResult struct {
214	N         int           // The number of iterations.
215	T         time.Duration // The total time taken.
216	Bytes     int64         // Bytes processed in one iteration.
217	MemAllocs uint64        // The total number of memory allocations.
218	MemBytes  uint64        // The total number of bytes allocated.
219}
220
221func (r BenchmarkResult) NsPerOp() int64 {
222	if r.N <= 0 {
223		return 0
224	}
225	return r.T.Nanoseconds() / int64(r.N)
226}
227
228func (r BenchmarkResult) mbPerSec() float64 {
229	if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 {
230		return 0
231	}
232	return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
233}
234
235func (r BenchmarkResult) AllocsPerOp() int64 {
236	if r.N <= 0 {
237		return 0
238	}
239	return int64(r.MemAllocs) / int64(r.N)
240}
241
242func (r BenchmarkResult) AllocedBytesPerOp() int64 {
243	if r.N <= 0 {
244		return 0
245	}
246	return int64(r.MemBytes) / int64(r.N)
247}
248
249func (r BenchmarkResult) String() string {
250	mbs := r.mbPerSec()
251	mb := ""
252	if mbs != 0 {
253		mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
254	}
255	nsop := r.NsPerOp()
256	ns := fmt.Sprintf("%10d ns/op", nsop)
257	if r.N > 0 && nsop < 100 {
258		// The format specifiers here make sure that
259		// the ones digits line up for all three possible formats.
260		if nsop < 10 {
261			ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
262		} else {
263			ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
264		}
265	}
266	return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb)
267}
268
269func (r BenchmarkResult) MemString() string {
270	return fmt.Sprintf("%8d B/op\t%8d allocs/op",
271		r.AllocedBytesPerOp(), r.AllocsPerOp())
272}
273
274// An internal function but exported because it is cross-package; part of the implementation
275// of the "go test" command.
276func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) {
277	// If no flag was specified, don't run benchmarks.
278	if len(*matchBenchmarks) == 0 {
279		return
280	}
281	for _, Benchmark := range benchmarks {
282		matched, err := matchString(*matchBenchmarks, Benchmark.Name)
283		if err != nil {
284			fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err)
285			os.Exit(1)
286		}
287		if !matched {
288			continue
289		}
290		for _, procs := range cpuList {
291			runtime.GOMAXPROCS(procs)
292			b := &B{
293				common: common{
294					signal: make(chan interface{}),
295				},
296				benchmark: Benchmark,
297			}
298			benchName := Benchmark.Name
299			if procs != 1 {
300				benchName = fmt.Sprintf("%s-%d", Benchmark.Name, procs)
301			}
302			fmt.Printf("%s\t", benchName)
303			r := b.run()
304			if b.failed {
305				// The output could be very long here, but probably isn't.
306				// We print it all, regardless, because we don't want to trim the reason
307				// the benchmark failed.
308				fmt.Printf("--- FAIL: %s\n%s", benchName, b.output)
309				continue
310			}
311			results := r.String()
312			if *benchmarkMemory || b.showAllocResult {
313				results += "\t" + r.MemString()
314			}
315			fmt.Println(results)
316			// Unlike with tests, we ignore the -chatty flag and always print output for
317			// benchmarks since the output generation time will skew the results.
318			if len(b.output) > 0 {
319				b.trimOutput()
320				fmt.Printf("--- BENCH: %s\n%s", benchName, b.output)
321			}
322			if p := runtime.GOMAXPROCS(-1); p != procs {
323				fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
324			}
325		}
326	}
327}
328
329// trimOutput shortens the output from a benchmark, which can be very long.
330func (b *B) trimOutput() {
331	// The output is likely to appear multiple times because the benchmark
332	// is run multiple times, but at least it will be seen. This is not a big deal
333	// because benchmarks rarely print, but just in case, we trim it if it's too long.
334	const maxNewlines = 10
335	for nlCount, j := 0, 0; j < len(b.output); j++ {
336		if b.output[j] == '\n' {
337			nlCount++
338			if nlCount >= maxNewlines {
339				b.output = append(b.output[:j], "\n\t... [output truncated]\n"...)
340				break
341			}
342		}
343	}
344}
345
346// Benchmark benchmarks a single function. Useful for creating
347// custom benchmarks that do not use the "go test" command.
348func Benchmark(f func(b *B)) BenchmarkResult {
349	b := &B{
350		common: common{
351			signal: make(chan interface{}),
352		},
353		benchmark: InternalBenchmark{"", f},
354	}
355	return b.run()
356}
357