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 if n < (2 * base) { 157 return 2 * base 158 } 159 if n < (5 * base) { 160 return 5 * base 161 } 162 return 10 * base 163} 164 165// run times the benchmark function in a separate goroutine. 166func (b *B) run() BenchmarkResult { 167 go b.launch() 168 <-b.signal 169 return b.result 170} 171 172// launch launches the benchmark function. It gradually increases the number 173// of benchmark iterations until the benchmark runs for a second in order 174// to get a reasonable measurement. It prints timing information in this form 175// testing.BenchmarkHello 100000 19 ns/op 176// launch is run by the fun function as a separate goroutine. 177func (b *B) launch() { 178 // Run the benchmark for a single iteration in case it's expensive. 179 n := 1 180 181 // Signal that we're done whether we return normally 182 // or by FailNow's runtime.Goexit. 183 defer func() { 184 b.signal <- b 185 }() 186 187 b.runN(n) 188 // Run the benchmark for at least the specified amount of time. 189 d := *benchTime 190 for !b.failed && b.duration < d && n < 1e9 { 191 last := n 192 // Predict iterations/sec. 193 if b.nsPerOp() == 0 { 194 n = 1e9 195 } else { 196 n = int(d.Nanoseconds() / b.nsPerOp()) 197 } 198 // Run more iterations than we think we'll need for a second (1.5x). 199 // Don't grow too fast in case we had timing errors previously. 200 // Be sure to run at least one more than last time. 201 n = max(min(n+n/2, 100*last), last+1) 202 // Round up to something easy to read. 203 n = roundUp(n) 204 b.runN(n) 205 } 206 b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes} 207} 208 209// The results of a benchmark run. 210type BenchmarkResult struct { 211 N int // The number of iterations. 212 T time.Duration // The total time taken. 213 Bytes int64 // Bytes processed in one iteration. 214 MemAllocs uint64 // The total number of memory allocations. 215 MemBytes uint64 // The total number of bytes allocated. 216} 217 218func (r BenchmarkResult) NsPerOp() int64 { 219 if r.N <= 0 { 220 return 0 221 } 222 return r.T.Nanoseconds() / int64(r.N) 223} 224 225func (r BenchmarkResult) mbPerSec() float64 { 226 if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 { 227 return 0 228 } 229 return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds() 230} 231 232func (r BenchmarkResult) AllocsPerOp() int64 { 233 if r.N <= 0 { 234 return 0 235 } 236 return int64(r.MemAllocs) / int64(r.N) 237} 238 239func (r BenchmarkResult) AllocedBytesPerOp() int64 { 240 if r.N <= 0 { 241 return 0 242 } 243 return int64(r.MemBytes) / int64(r.N) 244} 245 246func (r BenchmarkResult) String() string { 247 mbs := r.mbPerSec() 248 mb := "" 249 if mbs != 0 { 250 mb = fmt.Sprintf("\t%7.2f MB/s", mbs) 251 } 252 nsop := r.NsPerOp() 253 ns := fmt.Sprintf("%10d ns/op", nsop) 254 if r.N > 0 && nsop < 100 { 255 // The format specifiers here make sure that 256 // the ones digits line up for all three possible formats. 257 if nsop < 10 { 258 ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) 259 } else { 260 ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) 261 } 262 } 263 return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb) 264} 265 266func (r BenchmarkResult) MemString() string { 267 return fmt.Sprintf("%8d B/op\t%8d allocs/op", 268 r.AllocedBytesPerOp(), r.AllocsPerOp()) 269} 270 271// An internal function but exported because it is cross-package; part of the implementation 272// of the "go test" command. 273func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) { 274 // If no flag was specified, don't run benchmarks. 275 if len(*matchBenchmarks) == 0 { 276 return 277 } 278 for _, Benchmark := range benchmarks { 279 matched, err := matchString(*matchBenchmarks, Benchmark.Name) 280 if err != nil { 281 fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err) 282 os.Exit(1) 283 } 284 if !matched { 285 continue 286 } 287 for _, procs := range cpuList { 288 runtime.GOMAXPROCS(procs) 289 b := &B{ 290 common: common{ 291 signal: make(chan interface{}), 292 }, 293 benchmark: Benchmark, 294 } 295 benchName := Benchmark.Name 296 if procs != 1 { 297 benchName = fmt.Sprintf("%s-%d", Benchmark.Name, procs) 298 } 299 fmt.Printf("%s\t", benchName) 300 r := b.run() 301 if b.failed { 302 // The output could be very long here, but probably isn't. 303 // We print it all, regardless, because we don't want to trim the reason 304 // the benchmark failed. 305 fmt.Printf("--- FAIL: %s\n%s", benchName, b.output) 306 continue 307 } 308 results := r.String() 309 if *benchmarkMemory || b.showAllocResult { 310 results += "\t" + r.MemString() 311 } 312 fmt.Println(results) 313 // Unlike with tests, we ignore the -chatty flag and always print output for 314 // benchmarks since the output generation time will skew the results. 315 if len(b.output) > 0 { 316 b.trimOutput() 317 fmt.Printf("--- BENCH: %s\n%s", benchName, b.output) 318 } 319 if p := runtime.GOMAXPROCS(-1); p != procs { 320 fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p) 321 } 322 } 323 } 324} 325 326// trimOutput shortens the output from a benchmark, which can be very long. 327func (b *B) trimOutput() { 328 // The output is likely to appear multiple times because the benchmark 329 // is run multiple times, but at least it will be seen. This is not a big deal 330 // because benchmarks rarely print, but just in case, we trim it if it's too long. 331 const maxNewlines = 10 332 for nlCount, j := 0, 0; j < len(b.output); j++ { 333 if b.output[j] == '\n' { 334 nlCount++ 335 if nlCount >= maxNewlines { 336 b.output = append(b.output[:j], "\n\t... [output truncated]\n"...) 337 break 338 } 339 } 340 } 341} 342 343// Benchmark benchmarks a single function. Useful for creating 344// custom benchmarks that do not use the "go test" command. 345func Benchmark(f func(b *B)) BenchmarkResult { 346 b := &B{ 347 common: common{ 348 signal: make(chan interface{}), 349 }, 350 benchmark: InternalBenchmark{"", f}, 351 } 352 return b.run() 353} 354