]> Cypherpunks.ru repositories - gostls13.git/blob - src/testing/benchmark.go
testing: only compute b.N once when passed -count > 1
[gostls13.git] / src / testing / 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         "internal/race"
11         "os"
12         "runtime"
13         "sync"
14         "sync/atomic"
15         "time"
16 )
17
18 var matchBenchmarks = flag.String("test.bench", "", "run only benchmarks matching `regexp`")
19 var benchTime = flag.Duration("test.benchtime", 1*time.Second, "run each benchmark for duration `d`")
20 var benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks")
21
22 // Global lock to ensure only one benchmark runs at a time.
23 var benchmarkLock sync.Mutex
24
25 // Used for every benchmark for measuring memory.
26 var memStats runtime.MemStats
27
28 // An internal type but exported because it is cross-package; part of the implementation
29 // of the "go test" command.
30 type InternalBenchmark struct {
31         Name string
32         F    func(b *B)
33 }
34
35 // B is a type passed to Benchmark functions to manage benchmark
36 // timing and to specify the number of iterations to run.
37 //
38 // A benchmark ends when its Benchmark function returns or calls any of the methods
39 // FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods must be called
40 // only from the goroutine running the Benchmark function.
41 // The other reporting methods, such as the variations of Log and Error,
42 // may be called simultaneously from multiple goroutines.
43 //
44 // Like in tests, benchmark logs are accumulated during execution
45 // and dumped to standard error when done. Unlike in tests, benchmark logs
46 // are always printed, so as not to hide output whose existence may be
47 // affecting benchmark results.
48 type B struct {
49         common
50         importPath       string // import path of the package containing the benchmark
51         context          *benchContext
52         N                int
53         previousN        int           // number of iterations in the previous run
54         previousDuration time.Duration // total duration of the previous run
55         benchFunc        func(b *B)
56         benchTime        time.Duration
57         bytes            int64
58         missingBytes     bool // one of the subbenchmarks does not have bytes set.
59         timerOn          bool
60         showAllocResult  bool
61         result           BenchmarkResult
62         parallelism      int // RunParallel creates parallelism*GOMAXPROCS goroutines
63         // The initial states of memStats.Mallocs and memStats.TotalAlloc.
64         startAllocs uint64
65         startBytes  uint64
66         // The net total of this test after being run.
67         netAllocs uint64
68         netBytes  uint64
69 }
70
71 // StartTimer starts timing a test. This function is called automatically
72 // before a benchmark starts, but it can also used to resume timing after
73 // a call to StopTimer.
74 func (b *B) StartTimer() {
75         if !b.timerOn {
76                 runtime.ReadMemStats(&memStats)
77                 b.startAllocs = memStats.Mallocs
78                 b.startBytes = memStats.TotalAlloc
79                 b.start = time.Now()
80                 b.timerOn = true
81         }
82 }
83
84 // StopTimer stops timing a test. This can be used to pause the timer
85 // while performing complex initialization that you don't
86 // want to measure.
87 func (b *B) StopTimer() {
88         if b.timerOn {
89                 b.duration += time.Since(b.start)
90                 runtime.ReadMemStats(&memStats)
91                 b.netAllocs += memStats.Mallocs - b.startAllocs
92                 b.netBytes += memStats.TotalAlloc - b.startBytes
93                 b.timerOn = false
94         }
95 }
96
97 // ResetTimer zeros the elapsed benchmark time and memory allocation counters.
98 // It does not affect whether the timer is running.
99 func (b *B) ResetTimer() {
100         if b.timerOn {
101                 runtime.ReadMemStats(&memStats)
102                 b.startAllocs = memStats.Mallocs
103                 b.startBytes = memStats.TotalAlloc
104                 b.start = time.Now()
105         }
106         b.duration = 0
107         b.netAllocs = 0
108         b.netBytes = 0
109 }
110
111 // SetBytes records the number of bytes processed in a single operation.
112 // If this is called, the benchmark will report ns/op and MB/s.
113 func (b *B) SetBytes(n int64) { b.bytes = n }
114
115 // ReportAllocs enables malloc statistics for this benchmark.
116 // It is equivalent to setting -test.benchmem, but it only affects the
117 // benchmark function that calls ReportAllocs.
118 func (b *B) ReportAllocs() {
119         b.showAllocResult = true
120 }
121
122 func (b *B) nsPerOp() int64 {
123         if b.N <= 0 {
124                 return 0
125         }
126         return b.duration.Nanoseconds() / int64(b.N)
127 }
128
129 // runN runs a single benchmark for the specified number of iterations.
130 func (b *B) runN(n int) {
131         benchmarkLock.Lock()
132         defer benchmarkLock.Unlock()
133         // Try to get a comparable environment for each run
134         // by clearing garbage from previous runs.
135         runtime.GC()
136         b.raceErrors = -race.Errors()
137         b.N = n
138         b.parallelism = 1
139         b.ResetTimer()
140         b.StartTimer()
141         b.benchFunc(b)
142         b.StopTimer()
143         b.previousN = n
144         b.previousDuration = b.duration
145         b.raceErrors += race.Errors()
146         if b.raceErrors > 0 {
147                 b.Errorf("race detected during execution of benchmark")
148         }
149 }
150
151 func min(x, y int) int {
152         if x > y {
153                 return y
154         }
155         return x
156 }
157
158 func max(x, y int) int {
159         if x < y {
160                 return y
161         }
162         return x
163 }
164
165 // roundDown10 rounds a number down to the nearest power of 10.
166 func roundDown10(n int) int {
167         var tens = 0
168         // tens = floor(log_10(n))
169         for n >= 10 {
170                 n = n / 10
171                 tens++
172         }
173         // result = 10^tens
174         result := 1
175         for i := 0; i < tens; i++ {
176                 result *= 10
177         }
178         return result
179 }
180
181 // roundUp rounds x up to a number of the form [1eX, 2eX, 3eX, 5eX].
182 func roundUp(n int) int {
183         base := roundDown10(n)
184         switch {
185         case n <= base:
186                 return base
187         case n <= (2 * base):
188                 return 2 * base
189         case n <= (3 * base):
190                 return 3 * base
191         case n <= (5 * base):
192                 return 5 * base
193         default:
194                 return 10 * base
195         }
196 }
197
198 // run1 runs the first iteration of benchFunc. It returns whether more
199 // iterations of this benchmarks should be run.
200 func (b *B) run1() bool {
201         if ctx := b.context; ctx != nil {
202                 // Extend maxLen, if needed.
203                 if n := len(b.name) + ctx.extLen + 1; n > ctx.maxLen {
204                         ctx.maxLen = n + 8 // Add additional slack to avoid too many jumps in size.
205                 }
206         }
207         go func() {
208                 // Signal that we're done whether we return normally
209                 // or by FailNow's runtime.Goexit.
210                 defer func() {
211                         b.signal <- true
212                 }()
213
214                 b.runN(1)
215         }()
216         <-b.signal
217         if b.failed {
218                 fmt.Fprintf(b.w, "--- FAIL: %s\n%s", b.name, b.output)
219                 return false
220         }
221         // Only print the output if we know we are not going to proceed.
222         // Otherwise it is printed in processBench.
223         if atomic.LoadInt32(&b.hasSub) != 0 || b.finished {
224                 tag := "BENCH"
225                 if b.skipped {
226                         tag = "SKIP"
227                 }
228                 if b.chatty && (len(b.output) > 0 || b.finished) {
229                         b.trimOutput()
230                         fmt.Fprintf(b.w, "--- %s: %s\n%s", tag, b.name, b.output)
231                 }
232                 return false
233         }
234         return true
235 }
236
237 var labelsOnce sync.Once
238
239 // run executes the benchmark in a separate goroutine, including all of its
240 // subbenchmarks. b must not have subbenchmarks.
241 func (b *B) run() {
242         labelsOnce.Do(func() {
243                 fmt.Fprintf(b.w, "goos: %s\n", runtime.GOOS)
244                 fmt.Fprintf(b.w, "goarch: %s\n", runtime.GOARCH)
245                 if b.importPath != "" {
246                         fmt.Fprintf(b.w, "pkg: %s\n", b.importPath)
247                 }
248         })
249         if b.context != nil {
250                 // Running go test --test.bench
251                 b.context.processBench(b) // Must call doBench.
252         } else {
253                 // Running func Benchmark.
254                 b.doBench(0)
255         }
256 }
257
258 func (b *B) doBench(hint int) BenchmarkResult {
259         go b.launch(hint)
260         <-b.signal
261         return b.result
262 }
263
264 // autodetectN runs the benchmark function, gradually increasing the
265 // number of iterations until the benchmark runs for the requested
266 // benchtime.
267 func (b *B) autodetectN() {
268         // Run the benchmark for at least the specified amount of time.
269         d := b.benchTime
270         for n := 1; !b.failed && b.duration < d && n < 1e9; {
271                 last := n
272                 // Predict required iterations.
273                 n = int(d.Nanoseconds())
274                 if nsop := b.nsPerOp(); nsop != 0 {
275                         n /= int(nsop)
276                 }
277                 // Run more iterations than we think we'll need (1.2x).
278                 // Don't grow too fast in case we had timing errors previously.
279                 // Be sure to run at least one more than last time.
280                 n = max(min(n+n/5, 100*last), last+1)
281                 // Round up to something easy to read.
282                 n = roundUp(n)
283                 b.runN(n)
284         }
285 }
286
287 // launch launches the benchmark function for hintN iterations. If
288 // hintN == 0, it autodetects the number of benchmark iterations based
289 // on the requested benchtime.
290 // launch is run by the doBench function as a separate goroutine.
291 // run1 must have been called on b.
292 func (b *B) launch(hintN int) {
293         // Signal that we're done whether we return normally
294         // or by FailNow's runtime.Goexit.
295         defer func() {
296                 b.signal <- true
297         }()
298
299         if hintN == 0 {
300                 b.autodetectN()
301         } else {
302                 b.runN(hintN)
303         }
304
305         b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes}
306 }
307
308 // The results of a benchmark run.
309 type BenchmarkResult struct {
310         N         int           // The number of iterations.
311         T         time.Duration // The total time taken.
312         Bytes     int64         // Bytes processed in one iteration.
313         MemAllocs uint64        // The total number of memory allocations.
314         MemBytes  uint64        // The total number of bytes allocated.
315 }
316
317 func (r BenchmarkResult) NsPerOp() int64 {
318         if r.N <= 0 {
319                 return 0
320         }
321         return r.T.Nanoseconds() / int64(r.N)
322 }
323
324 func (r BenchmarkResult) mbPerSec() float64 {
325         if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 {
326                 return 0
327         }
328         return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
329 }
330
331 // AllocsPerOp returns r.MemAllocs / r.N.
332 func (r BenchmarkResult) AllocsPerOp() int64 {
333         if r.N <= 0 {
334                 return 0
335         }
336         return int64(r.MemAllocs) / int64(r.N)
337 }
338
339 // AllocedBytesPerOp returns r.MemBytes / r.N.
340 func (r BenchmarkResult) AllocedBytesPerOp() int64 {
341         if r.N <= 0 {
342                 return 0
343         }
344         return int64(r.MemBytes) / int64(r.N)
345 }
346
347 func (r BenchmarkResult) String() string {
348         mbs := r.mbPerSec()
349         mb := ""
350         if mbs != 0 {
351                 mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
352         }
353         nsop := r.NsPerOp()
354         ns := fmt.Sprintf("%10d ns/op", nsop)
355         if r.N > 0 && nsop < 100 {
356                 // The format specifiers here make sure that
357                 // the ones digits line up for all three possible formats.
358                 if nsop < 10 {
359                         ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
360                 } else {
361                         ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
362                 }
363         }
364         return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb)
365 }
366
367 // MemString returns r.AllocedBytesPerOp and r.AllocsPerOp in the same format as 'go test'.
368 func (r BenchmarkResult) MemString() string {
369         return fmt.Sprintf("%8d B/op\t%8d allocs/op",
370                 r.AllocedBytesPerOp(), r.AllocsPerOp())
371 }
372
373 // benchmarkName returns full name of benchmark including procs suffix.
374 func benchmarkName(name string, n int) string {
375         if n != 1 {
376                 return fmt.Sprintf("%s-%d", name, n)
377         }
378         return name
379 }
380
381 type benchContext struct {
382         match *matcher
383
384         maxLen int // The largest recorded benchmark name.
385         extLen int // Maximum extension length.
386 }
387
388 // An internal function but exported because it is cross-package; part of the implementation
389 // of the "go test" command.
390 func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) {
391         runBenchmarks("", matchString, benchmarks)
392 }
393
394 func runBenchmarks(importPath string, matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) bool {
395         // If no flag was specified, don't run benchmarks.
396         if len(*matchBenchmarks) == 0 {
397                 return true
398         }
399         // Collect matching benchmarks and determine longest name.
400         maxprocs := 1
401         for _, procs := range cpuList {
402                 if procs > maxprocs {
403                         maxprocs = procs
404                 }
405         }
406         ctx := &benchContext{
407                 match:  newMatcher(matchString, *matchBenchmarks, "-test.bench"),
408                 extLen: len(benchmarkName("", maxprocs)),
409         }
410         var bs []InternalBenchmark
411         for _, Benchmark := range benchmarks {
412                 if _, matched, _ := ctx.match.fullName(nil, Benchmark.Name); matched {
413                         bs = append(bs, Benchmark)
414                         benchName := benchmarkName(Benchmark.Name, maxprocs)
415                         if l := len(benchName) + ctx.extLen + 1; l > ctx.maxLen {
416                                 ctx.maxLen = l
417                         }
418                 }
419         }
420         main := &B{
421                 common: common{
422                         name:   "Main",
423                         w:      os.Stdout,
424                         chatty: *chatty,
425                 },
426                 importPath: importPath,
427                 benchFunc: func(b *B) {
428                         for _, Benchmark := range bs {
429                                 b.Run(Benchmark.Name, Benchmark.F)
430                         }
431                 },
432                 benchTime: *benchTime,
433                 context:   ctx,
434         }
435         main.runN(1)
436         return !main.failed
437 }
438
439 // processBench runs bench b for the configured CPU counts and prints the results.
440 func (ctx *benchContext) processBench(b *B) {
441         for i, procs := range cpuList {
442                 var nHint int
443                 for j := uint(0); j < *count; j++ {
444                         runtime.GOMAXPROCS(procs)
445                         benchName := benchmarkName(b.name, procs)
446                         fmt.Fprintf(b.w, "%-*s\t", ctx.maxLen, benchName)
447                         // Recompute the running time for all but the first iteration.
448                         if i > 0 || j > 0 {
449                                 b = &B{
450                                         common: common{
451                                                 signal: make(chan bool),
452                                                 name:   b.name,
453                                                 w:      b.w,
454                                                 chatty: b.chatty,
455                                         },
456                                         benchFunc: b.benchFunc,
457                                         benchTime: b.benchTime,
458                                 }
459                                 b.run1()
460                         }
461                         r := b.doBench(nHint)
462                         if j == 0 {
463                                 nHint = b.N
464                         }
465                         if b.failed {
466                                 // The output could be very long here, but probably isn't.
467                                 // We print it all, regardless, because we don't want to trim the reason
468                                 // the benchmark failed.
469                                 fmt.Fprintf(b.w, "--- FAIL: %s\n%s", benchName, b.output)
470                                 continue
471                         }
472                         results := r.String()
473                         if *benchmarkMemory || b.showAllocResult {
474                                 results += "\t" + r.MemString()
475                         }
476                         fmt.Fprintln(b.w, results)
477                         // Unlike with tests, we ignore the -chatty flag and always print output for
478                         // benchmarks since the output generation time will skew the results.
479                         if len(b.output) > 0 {
480                                 b.trimOutput()
481                                 fmt.Fprintf(b.w, "--- BENCH: %s\n%s", benchName, b.output)
482                         }
483                         if p := runtime.GOMAXPROCS(-1); p != procs {
484                                 fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
485                         }
486                 }
487         }
488 }
489
490 // Run benchmarks f as a subbenchmark with the given name. It reports
491 // whether there were any failures.
492 //
493 // A subbenchmark is like any other benchmark. A benchmark that calls Run at
494 // least once will not be measured itself and will be called once with N=1.
495 func (b *B) Run(name string, f func(b *B)) bool {
496         // Since b has subbenchmarks, we will no longer run it as a benchmark itself.
497         // Release the lock and acquire it on exit to ensure locks stay paired.
498         atomic.StoreInt32(&b.hasSub, 1)
499         benchmarkLock.Unlock()
500         defer benchmarkLock.Lock()
501
502         benchName, ok, partial := b.name, true, false
503         if b.context != nil {
504                 benchName, ok, partial = b.context.match.fullName(&b.common, name)
505         }
506         if !ok {
507                 return true
508         }
509         sub := &B{
510                 common: common{
511                         signal: make(chan bool),
512                         name:   benchName,
513                         parent: &b.common,
514                         level:  b.level + 1,
515                         w:      b.w,
516                         chatty: b.chatty,
517                 },
518                 importPath: b.importPath,
519                 benchFunc:  f,
520                 benchTime:  b.benchTime,
521                 context:    b.context,
522         }
523         if partial {
524                 // Partial name match, like -bench=X/Y matching BenchmarkX.
525                 // Only process sub-benchmarks, if any.
526                 atomic.StoreInt32(&sub.hasSub, 1)
527         }
528         if sub.run1() {
529                 sub.run()
530         }
531         b.add(sub.result)
532         return !sub.failed
533 }
534
535 // add simulates running benchmarks in sequence in a single iteration. It is
536 // used to give some meaningful results in case func Benchmark is used in
537 // combination with Run.
538 func (b *B) add(other BenchmarkResult) {
539         r := &b.result
540         // The aggregated BenchmarkResults resemble running all subbenchmarks as
541         // in sequence in a single benchmark.
542         r.N = 1
543         r.T += time.Duration(other.NsPerOp())
544         if other.Bytes == 0 {
545                 // Summing Bytes is meaningless in aggregate if not all subbenchmarks
546                 // set it.
547                 b.missingBytes = true
548                 r.Bytes = 0
549         }
550         if !b.missingBytes {
551                 r.Bytes += other.Bytes
552         }
553         r.MemAllocs += uint64(other.AllocsPerOp())
554         r.MemBytes += uint64(other.AllocedBytesPerOp())
555 }
556
557 // trimOutput shortens the output from a benchmark, which can be very long.
558 func (b *B) trimOutput() {
559         // The output is likely to appear multiple times because the benchmark
560         // is run multiple times, but at least it will be seen. This is not a big deal
561         // because benchmarks rarely print, but just in case, we trim it if it's too long.
562         const maxNewlines = 10
563         for nlCount, j := 0, 0; j < len(b.output); j++ {
564                 if b.output[j] == '\n' {
565                         nlCount++
566                         if nlCount >= maxNewlines {
567                                 b.output = append(b.output[:j], "\n\t... [output truncated]\n"...)
568                                 break
569                         }
570                 }
571         }
572 }
573
574 // A PB is used by RunParallel for running parallel benchmarks.
575 type PB struct {
576         globalN *uint64 // shared between all worker goroutines iteration counter
577         grain   uint64  // acquire that many iterations from globalN at once
578         cache   uint64  // local cache of acquired iterations
579         bN      uint64  // total number of iterations to execute (b.N)
580 }
581
582 // Next reports whether there are more iterations to execute.
583 func (pb *PB) Next() bool {
584         if pb.cache == 0 {
585                 n := atomic.AddUint64(pb.globalN, pb.grain)
586                 if n <= pb.bN {
587                         pb.cache = pb.grain
588                 } else if n < pb.bN+pb.grain {
589                         pb.cache = pb.bN + pb.grain - n
590                 } else {
591                         return false
592                 }
593         }
594         pb.cache--
595         return true
596 }
597
598 // RunParallel runs a benchmark in parallel.
599 // It creates multiple goroutines and distributes b.N iterations among them.
600 // The number of goroutines defaults to GOMAXPROCS. To increase parallelism for
601 // non-CPU-bound benchmarks, call SetParallelism before RunParallel.
602 // RunParallel is usually used with the go test -cpu flag.
603 //
604 // The body function will be run in each goroutine. It should set up any
605 // goroutine-local state and then iterate until pb.Next returns false.
606 // It should not use the StartTimer, StopTimer, or ResetTimer functions,
607 // because they have global effect. It should also not call Run.
608 func (b *B) RunParallel(body func(*PB)) {
609         if b.N == 0 {
610                 return // Nothing to do when probing.
611         }
612         // Calculate grain size as number of iterations that take ~100µs.
613         // 100µs is enough to amortize the overhead and provide sufficient
614         // dynamic load balancing.
615         grain := uint64(0)
616         if b.previousN > 0 && b.previousDuration > 0 {
617                 grain = 1e5 * uint64(b.previousN) / uint64(b.previousDuration)
618         }
619         if grain < 1 {
620                 grain = 1
621         }
622         // We expect the inner loop and function call to take at least 10ns,
623         // so do not do more than 100µs/10ns=1e4 iterations.
624         if grain > 1e4 {
625                 grain = 1e4
626         }
627
628         n := uint64(0)
629         numProcs := b.parallelism * runtime.GOMAXPROCS(0)
630         var wg sync.WaitGroup
631         wg.Add(numProcs)
632         for p := 0; p < numProcs; p++ {
633                 go func() {
634                         defer wg.Done()
635                         pb := &PB{
636                                 globalN: &n,
637                                 grain:   grain,
638                                 bN:      uint64(b.N),
639                         }
640                         body(pb)
641                 }()
642         }
643         wg.Wait()
644         if n <= uint64(b.N) && !b.Failed() {
645                 b.Fatal("RunParallel: body exited without pb.Next() == false")
646         }
647 }
648
649 // SetParallelism sets the number of goroutines used by RunParallel to p*GOMAXPROCS.
650 // There is usually no need to call SetParallelism for CPU-bound benchmarks.
651 // If p is less than 1, this call will have no effect.
652 func (b *B) SetParallelism(p int) {
653         if p >= 1 {
654                 b.parallelism = p
655         }
656 }
657
658 // Benchmark benchmarks a single function. Useful for creating
659 // custom benchmarks that do not use the "go test" command.
660 //
661 // If f calls Run, the result will be an estimate of running all its
662 // subbenchmarks that don't call Run in sequence in a single benchmark.
663 func Benchmark(f func(b *B)) BenchmarkResult {
664         b := &B{
665                 common: common{
666                         signal: make(chan bool),
667                         w:      discard{},
668                 },
669                 benchFunc: f,
670                 benchTime: *benchTime,
671         }
672         if b.run1() {
673                 b.run()
674         }
675         return b.result
676 }
677
678 type discard struct{}
679
680 func (discard) Write(b []byte) (n int, err error) { return len(b), nil }