github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/testing/benchmark.go (about) 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 // This file has been modified for use by the TinyGo compiler. 6 7 package testing 8 9 import ( 10 "flag" 11 "fmt" 12 "io" 13 "math" 14 "os" 15 "runtime" 16 "strconv" 17 "strings" 18 "time" 19 ) 20 21 func initBenchmarkFlags() { 22 matchBenchmarks = flag.String("test.bench", "", "run only benchmarks matching `regexp`") 23 benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks") 24 flag.Var(&benchTime, "test.benchtime", "run each benchmark for duration `d`") 25 } 26 27 var ( 28 matchBenchmarks *string 29 benchmarkMemory *bool 30 benchTime = benchTimeFlag{d: 1 * time.Second} // changed during test of testing package 31 ) 32 33 type benchTimeFlag struct { 34 d time.Duration 35 n int 36 } 37 38 func (f *benchTimeFlag) String() string { 39 if f.n > 0 { 40 return fmt.Sprintf("%dx", f.n) 41 } 42 return time.Duration(f.d).String() 43 } 44 45 func (f *benchTimeFlag) Set(s string) error { 46 if strings.HasSuffix(s, "x") { 47 n, err := strconv.ParseInt(s[:len(s)-1], 10, 0) 48 if err != nil || n <= 0 { 49 return fmt.Errorf("invalid count") 50 } 51 *f = benchTimeFlag{n: int(n)} 52 return nil 53 } 54 d, err := time.ParseDuration(s) 55 if err != nil || d <= 0 { 56 return fmt.Errorf("invalid duration") 57 } 58 *f = benchTimeFlag{d: d} 59 return nil 60 } 61 62 // InternalBenchmark is an internal type but exported because it is cross-package; 63 // it is part of the implementation of the "go test" command. 64 type InternalBenchmark struct { 65 Name string 66 F func(b *B) 67 } 68 69 // B is a type passed to Benchmark functions to manage benchmark 70 // timing and to specify the number of iterations to run. 71 // 72 // A benchmark ends when its Benchmark function returns or calls any of the methods 73 // FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods must be called 74 // only from the goroutine running the Benchmark function. 75 // The other reporting methods, such as the variations of Log and Error, 76 // may be called simultaneously from multiple goroutines. 77 // 78 // Like in tests, benchmark logs are accumulated during execution 79 // and dumped to standard output when done. Unlike in tests, benchmark logs 80 // are always printed, so as not to hide output whose existence may be 81 // affecting benchmark results. 82 type B struct { 83 common 84 context *benchContext 85 N int 86 benchFunc func(b *B) 87 bytes int64 88 missingBytes bool // one of the subbenchmarks does not have bytes set. 89 benchTime benchTimeFlag 90 timerOn bool 91 result BenchmarkResult 92 93 // report memory statistics 94 showAllocResult bool 95 // initial state of MemStats.Mallocs and MemStats.TotalAlloc 96 startAllocs uint64 97 startBytes uint64 98 // net total after running benchmar 99 netAllocs uint64 100 netBytes uint64 101 } 102 103 // StartTimer starts timing a test. This function is called automatically 104 // before a benchmark starts, but it can also be used to resume timing after 105 // a call to StopTimer. 106 func (b *B) StartTimer() { 107 if !b.timerOn { 108 b.start = time.Now() 109 b.timerOn = true 110 111 var mstats runtime.MemStats 112 runtime.ReadMemStats(&mstats) 113 b.startAllocs = mstats.Mallocs 114 b.startBytes = mstats.TotalAlloc 115 } 116 } 117 118 // StopTimer stops timing a test. This can be used to pause the timer 119 // while performing complex initialization that you don't 120 // want to measure. 121 func (b *B) StopTimer() { 122 if b.timerOn { 123 b.duration += time.Since(b.start) 124 b.timerOn = false 125 126 var mstats runtime.MemStats 127 runtime.ReadMemStats(&mstats) 128 b.netAllocs += mstats.Mallocs - b.startAllocs 129 b.netBytes += mstats.TotalAlloc - b.startBytes 130 } 131 } 132 133 // ResetTimer zeroes the elapsed benchmark time and memory allocation counters 134 // and deletes user-reported metrics. 135 func (b *B) ResetTimer() { 136 if b.timerOn { 137 b.start = time.Now() 138 139 var mstats runtime.MemStats 140 runtime.ReadMemStats(&mstats) 141 b.startAllocs = mstats.Mallocs 142 b.startBytes = mstats.TotalAlloc 143 } 144 b.duration = 0 145 b.netAllocs = 0 146 b.netBytes = 0 147 } 148 149 // SetBytes records the number of bytes processed in a single operation. 150 // If this is called, the benchmark will report ns/op and MB/s. 151 func (b *B) SetBytes(n int64) { b.bytes = n } 152 153 // ReportAllocs enables malloc statistics for this benchmark. 154 // It is equivalent to setting -test.benchmem, but it only affects the 155 // benchmark function that calls ReportAllocs. 156 func (b *B) ReportAllocs() { 157 b.showAllocResult = true 158 } 159 160 // runN runs a single benchmark for the specified number of iterations. 161 func (b *B) runN(n int) { 162 b.N = n 163 runtime.GC() 164 b.ResetTimer() 165 b.StartTimer() 166 b.benchFunc(b) 167 b.StopTimer() 168 } 169 170 func min(x, y int64) int64 { 171 if x > y { 172 return y 173 } 174 return x 175 } 176 177 func max(x, y int64) int64 { 178 if x < y { 179 return y 180 } 181 return x 182 } 183 184 // run1 runs the first iteration of benchFunc. It reports whether more 185 // iterations of this benchmarks should be run. 186 func (b *B) run1() bool { 187 if ctx := b.context; ctx != nil { 188 // Extend maxLen, if needed. 189 if n := len(b.name); n > ctx.maxLen { 190 ctx.maxLen = n + 8 // Add additional slack to avoid too many jumps in size. 191 } 192 } 193 b.runN(1) 194 return !b.hasSub 195 } 196 197 // run executes the benchmark. 198 func (b *B) run() { 199 if b.context != nil { 200 // Running go test --test.bench 201 b.processBench(b.context) // calls doBench and prints results 202 } else { 203 // Running func Benchmark. 204 b.doBench() 205 } 206 } 207 208 func (b *B) doBench() BenchmarkResult { 209 // in upstream, this uses a goroutine 210 b.launch() 211 return b.result 212 } 213 214 // launch launches the benchmark function. It gradually increases the number 215 // of benchmark iterations until the benchmark runs for the requested benchtime. 216 // run1 must have been called on b. 217 func (b *B) launch() { 218 // Run the benchmark for at least the specified amount of time. 219 if b.benchTime.n > 0 { 220 b.runN(b.benchTime.n) 221 } else { 222 d := b.benchTime.d 223 b.failed = false 224 b.duration = 0 225 for n := int64(1); !b.failed && b.duration < d && n < 1e9; { 226 last := n 227 // Predict required iterations. 228 goalns := d.Nanoseconds() 229 prevIters := int64(b.N) 230 prevns := b.duration.Nanoseconds() 231 if prevns <= 0 { 232 // Round up, to avoid div by zero. 233 prevns = 1 234 } 235 // Order of operations matters. 236 // For very fast benchmarks, prevIters ~= prevns. 237 // If you divide first, you get 0 or 1, 238 // which can hide an order of magnitude in execution time. 239 // So multiply first, then divide. 240 n = goalns * prevIters / prevns 241 // Run more iterations than we think we'll need (1.2x). 242 n += n / 5 243 // Don't grow too fast in case we had timing errors previously. 244 n = min(n, 100*last) 245 // Be sure to run at least one more than last time. 246 n = max(n, last+1) 247 // Don't run more than 1e9 times. (This also keeps n in int range on 32 bit platforms.) 248 n = min(n, 1e9) 249 b.runN(int(n)) 250 } 251 } 252 b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes} 253 } 254 255 // BenchmarkResult contains the results of a benchmark run. 256 type BenchmarkResult struct { 257 N int // The number of iterations. 258 T time.Duration // The total time taken. 259 Bytes int64 // Bytes processed in one iteration. 260 261 MemAllocs uint64 // The total number of memory allocations. 262 MemBytes uint64 // The total number of bytes allocated. 263 } 264 265 // NsPerOp returns the "ns/op" metric. 266 func (r BenchmarkResult) NsPerOp() int64 { 267 if r.N <= 0 { 268 return 0 269 } 270 return r.T.Nanoseconds() / int64(r.N) 271 } 272 273 // mbPerSec returns the "MB/s" metric. 274 func (r BenchmarkResult) mbPerSec() float64 { 275 if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 { 276 return 0 277 } 278 return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds() 279 } 280 281 // AllocsPerOp returns the "allocs/op" metric, 282 // which is calculated as r.MemAllocs / r.N. 283 func (r BenchmarkResult) AllocsPerOp() int64 { 284 if r.N <= 0 { 285 return 0 286 } 287 return int64(r.MemAllocs) / int64(r.N) 288 } 289 290 // AllocedBytesPerOp returns the "B/op" metric, 291 // which is calculated as r.MemBytes / r.N. 292 func (r BenchmarkResult) AllocedBytesPerOp() int64 { 293 if r.N <= 0 { 294 return 0 295 } 296 return int64(r.MemBytes) / int64(r.N) 297 } 298 299 // String returns a summary of the benchmark results. 300 // It follows the benchmark result line format from 301 // https://golang.org/design/14313-benchmark-format, not including the 302 // benchmark name. 303 // Extra metrics override built-in metrics of the same name. 304 // String does not include allocs/op or B/op, since those are reported 305 // by MemString. 306 func (r BenchmarkResult) String() string { 307 buf := new(strings.Builder) 308 fmt.Fprintf(buf, "%8d", r.N) 309 310 // Get ns/op as a float. 311 ns := float64(r.T.Nanoseconds()) / float64(r.N) 312 if ns != 0 { 313 buf.WriteByte('\t') 314 prettyPrint(buf, ns, "ns/op") 315 } 316 317 if mbs := r.mbPerSec(); mbs != 0 { 318 fmt.Fprintf(buf, "\t%7.2f MB/s", mbs) 319 } 320 return buf.String() 321 } 322 323 // MemString returns r.AllocedBytesPerOp and r.AllocsPerOp in the same format as 'go test'. 324 func (r BenchmarkResult) MemString() string { 325 return fmt.Sprintf("%8d B/op\t%8d allocs/op", 326 r.AllocedBytesPerOp(), r.AllocsPerOp()) 327 } 328 329 func prettyPrint(w io.Writer, x float64, unit string) { 330 // Print all numbers with 10 places before the decimal point 331 // and small numbers with four sig figs. Field widths are 332 // chosen to fit the whole part in 10 places while aligning 333 // the decimal point of all fractional formats. 334 var format string 335 switch y := math.Abs(x); { 336 case y == 0 || y >= 999.95: 337 format = "%10.0f %s" 338 case y >= 99.995: 339 format = "%12.1f %s" 340 case y >= 9.9995: 341 format = "%13.2f %s" 342 case y >= 0.99995: 343 format = "%14.3f %s" 344 case y >= 0.099995: 345 format = "%15.4f %s" 346 case y >= 0.0099995: 347 format = "%16.5f %s" 348 case y >= 0.00099995: 349 format = "%17.6f %s" 350 default: 351 format = "%18.7f %s" 352 } 353 fmt.Fprintf(w, format, x, unit) 354 } 355 356 type benchContext struct { 357 match *matcher 358 359 maxLen int // The largest recorded benchmark name. 360 } 361 362 func runBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) bool { 363 // If no flag was specified, don't run benchmarks. 364 if len(*matchBenchmarks) == 0 { 365 return true 366 } 367 ctx := &benchContext{ 368 match: newMatcher(matchString, *matchBenchmarks, "-test.bench", flagSkipRegexp), 369 } 370 var bs []InternalBenchmark 371 for _, Benchmark := range benchmarks { 372 if _, matched, _ := ctx.match.fullName(nil, Benchmark.Name); matched { 373 bs = append(bs, Benchmark) 374 benchName := Benchmark.Name 375 if l := len(benchName); l > ctx.maxLen { 376 ctx.maxLen = l 377 } 378 } 379 } 380 main := &B{ 381 common: common{ 382 output: &logger{}, 383 name: "Main", 384 }, 385 benchTime: benchTime, 386 benchFunc: func(b *B) { 387 for _, Benchmark := range bs { 388 b.Run(Benchmark.Name, Benchmark.F) 389 } 390 }, 391 context: ctx, 392 } 393 394 main.runN(1) 395 return true 396 } 397 398 // processBench runs bench b and prints the results. 399 func (b *B) processBench(ctx *benchContext) { 400 benchName := b.name 401 402 for i := 0; i < flagCount; i++ { 403 if ctx != nil { 404 fmt.Printf("%-*s\t", ctx.maxLen, benchName) 405 } 406 r := b.doBench() 407 if b.failed { 408 // The output could be very long here, but probably isn't. 409 // We print it all, regardless, because we don't want to trim the reason 410 // the benchmark failed. 411 fmt.Printf("--- FAIL: %s\n%s", benchName, "") // b.output) 412 return 413 } 414 if ctx != nil { 415 results := r.String() 416 417 if *benchmarkMemory || b.showAllocResult { 418 results += "\t" + r.MemString() 419 } 420 fmt.Println(results) 421 422 // Print any benchmark output 423 if b.output.Len() > 0 { 424 fmt.Printf("--- BENCH: %s\n", benchName) 425 b.output.WriteTo(os.Stdout) 426 } 427 } 428 } 429 } 430 431 // Run benchmarks f as a subbenchmark with the given name. It reports 432 // true if the subbenchmark succeeded. 433 // 434 // A subbenchmark is like any other benchmark. A benchmark that calls Run at 435 // least once will not be measured itself and will be called once with N=1. 436 func (b *B) Run(name string, f func(b *B)) bool { 437 benchName, ok, partial := b.name, true, false 438 if b.context != nil { 439 benchName, ok, partial = b.context.match.fullName(&b.common, name) 440 } 441 if !ok { 442 return true 443 } 444 b.hasSub = true 445 sub := &B{ 446 common: common{ 447 output: &logger{}, 448 name: benchName, 449 level: b.level + 1, 450 }, 451 benchFunc: f, 452 benchTime: b.benchTime, 453 context: b.context, 454 } 455 if partial { 456 // Partial name match, like -bench=X/Y matching BenchmarkX. 457 // Only process sub-benchmarks, if any. 458 sub.hasSub = true 459 } 460 if sub.run1() { 461 sub.run() 462 } 463 b.add(sub.result) 464 return !sub.failed 465 } 466 467 // add simulates running benchmarks in sequence in a single iteration. It is 468 // used to give some meaningful results in case func Benchmark is used in 469 // combination with Run. 470 func (b *B) add(other BenchmarkResult) { 471 r := &b.result 472 // The aggregated BenchmarkResults resemble running all subbenchmarks as 473 // in sequence in a single benchmark. 474 r.N = 1 475 r.T += time.Duration(other.NsPerOp()) 476 if other.Bytes == 0 { 477 // Summing Bytes is meaningless in aggregate if not all subbenchmarks 478 // set it. 479 b.missingBytes = true 480 r.Bytes = 0 481 } 482 if !b.missingBytes { 483 r.Bytes += other.Bytes 484 } 485 } 486 487 // A PB is used by RunParallel for running parallel benchmarks. 488 type PB struct { 489 } 490 491 // Next reports whether there are more iterations to execute. 492 func (pb *PB) Next() bool { 493 return false 494 } 495 496 // RunParallel runs a benchmark in parallel. 497 // 498 // Not implemented 499 func (b *B) RunParallel(body func(*PB)) { 500 return 501 } 502 503 // Benchmark benchmarks a single function. It is useful for creating 504 // custom benchmarks that do not use the "go test" command. 505 // 506 // If f calls Run, the result will be an estimate of running all its 507 // subbenchmarks that don't call Run in sequence in a single benchmark. 508 func Benchmark(f func(b *B)) BenchmarkResult { 509 b := &B{ 510 benchFunc: f, 511 benchTime: benchTime, 512 } 513 if b.run1() { 514 b.run() 515 } 516 return b.result 517 }