Source file src/go/doc/testdata/benchmark.go

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

View as plain text