| // 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(); |
| } |
| } |