Source file
src/testing/benchmark.go
1
2
3
4
5 package testing
6
7 import (
8 "flag"
9 "fmt"
10 "internal/race"
11 "internal/sysinfo"
12 "io"
13 "math"
14 "os"
15 "runtime"
16 "sort"
17 "strconv"
18 "strings"
19 "sync"
20 "sync/atomic"
21 "time"
22 "unicode"
23 )
24
25 func initBenchmarkFlags() {
26 matchBenchmarks = flag.String("test.bench", "", "run only benchmarks matching `regexp`")
27 benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks")
28 flag.Var(&benchTime, "test.benchtime", "run each benchmark for duration `d`")
29 }
30
31 var (
32 matchBenchmarks *string
33 benchmarkMemory *bool
34
35 benchTime = durationOrCountFlag{d: 1 * time.Second}
36 )
37
38 type durationOrCountFlag struct {
39 d time.Duration
40 n int
41 allowZero bool
42 }
43
44 func (f *durationOrCountFlag) String() string {
45 if f.n > 0 {
46 return fmt.Sprintf("%dx", f.n)
47 }
48 return f.d.String()
49 }
50
51 func (f *durationOrCountFlag) Set(s string) error {
52 if strings.HasSuffix(s, "x") {
53 n, err := strconv.ParseInt(s[:len(s)-1], 10, 0)
54 if err != nil || n < 0 || (!f.allowZero && n == 0) {
55 return fmt.Errorf("invalid count")
56 }
57 *f = durationOrCountFlag{n: int(n)}
58 return nil
59 }
60 d, err := time.ParseDuration(s)
61 if err != nil || d < 0 || (!f.allowZero && d == 0) {
62 return fmt.Errorf("invalid duration")
63 }
64 *f = durationOrCountFlag{d: d}
65 return nil
66 }
67
68
69 var benchmarkLock sync.Mutex
70
71
72 var memStats runtime.MemStats
73
74
75
76 type InternalBenchmark struct {
77 Name string
78 F func(b *B)
79 }
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 type B struct {
95 common
96 importPath string
97 context *benchContext
98 N int
99 previousN int
100 previousDuration time.Duration
101 benchFunc func(b *B)
102 benchTime durationOrCountFlag
103 bytes int64
104 missingBytes bool
105 timerOn bool
106 showAllocResult bool
107 result BenchmarkResult
108 parallelism int
109
110 startAllocs uint64
111 startBytes uint64
112
113 netAllocs uint64
114 netBytes uint64
115
116 extra map[string]float64
117 }
118
119
120
121
122 func (b *B) StartTimer() {
123 if !b.timerOn {
124 runtime.ReadMemStats(&memStats)
125 b.startAllocs = memStats.Mallocs
126 b.startBytes = memStats.TotalAlloc
127 b.start = time.Now()
128 b.timerOn = true
129 }
130 }
131
132
133
134
135 func (b *B) StopTimer() {
136 if b.timerOn {
137 b.duration += time.Since(b.start)
138 runtime.ReadMemStats(&memStats)
139 b.netAllocs += memStats.Mallocs - b.startAllocs
140 b.netBytes += memStats.TotalAlloc - b.startBytes
141 b.timerOn = false
142 }
143 }
144
145
146
147
148 func (b *B) ResetTimer() {
149 if b.extra == nil {
150
151
152 b.extra = make(map[string]float64, 16)
153 } else {
154 for k := range b.extra {
155 delete(b.extra, k)
156 }
157 }
158 if b.timerOn {
159 runtime.ReadMemStats(&memStats)
160 b.startAllocs = memStats.Mallocs
161 b.startBytes = memStats.TotalAlloc
162 b.start = time.Now()
163 }
164 b.duration = 0
165 b.netAllocs = 0
166 b.netBytes = 0
167 }
168
169
170
171 func (b *B) SetBytes(n int64) { b.bytes = n }
172
173
174
175
176 func (b *B) ReportAllocs() {
177 b.showAllocResult = true
178 }
179
180
181 func (b *B) runN(n int) {
182 benchmarkLock.Lock()
183 defer benchmarkLock.Unlock()
184 defer b.runCleanup(normalPanic)
185
186
187 runtime.GC()
188 b.raceErrors = -race.Errors()
189 b.N = n
190 b.parallelism = 1
191 b.ResetTimer()
192 b.StartTimer()
193 b.benchFunc(b)
194 b.StopTimer()
195 b.previousN = n
196 b.previousDuration = b.duration
197 b.raceErrors += race.Errors()
198 if b.raceErrors > 0 {
199 b.Errorf("race detected during execution of benchmark")
200 }
201 }
202
203 func min(x, y int64) int64 {
204 if x > y {
205 return y
206 }
207 return x
208 }
209
210 func max(x, y int64) int64 {
211 if x < y {
212 return y
213 }
214 return x
215 }
216
217
218
219 func (b *B) run1() bool {
220 if ctx := b.context; ctx != nil {
221
222 if n := len(b.name) + ctx.extLen + 1; n > ctx.maxLen {
223 ctx.maxLen = n + 8
224 }
225 }
226 go func() {
227
228
229 defer func() {
230 b.signal <- true
231 }()
232
233 b.runN(1)
234 }()
235 <-b.signal
236 if b.failed {
237 fmt.Fprintf(b.w, "%s--- FAIL: %s\n%s", b.chatty.prefix(), b.name, b.output)
238 return false
239 }
240
241
242 b.mu.RLock()
243 finished := b.finished
244 b.mu.RUnlock()
245 if b.hasSub.Load() || finished {
246 tag := "BENCH"
247 if b.skipped {
248 tag = "SKIP"
249 }
250 if b.chatty != nil && (len(b.output) > 0 || finished) {
251 b.trimOutput()
252 fmt.Fprintf(b.w, "%s--- %s: %s\n%s", b.chatty.prefix(), tag, b.name, b.output)
253 }
254 return false
255 }
256 return true
257 }
258
259 var labelsOnce sync.Once
260
261
262
263 func (b *B) run() {
264 labelsOnce.Do(func() {
265 fmt.Fprintf(b.w, "goos: %s\n", runtime.GOOS)
266 fmt.Fprintf(b.w, "goarch: %s\n", runtime.GOARCH)
267 if b.importPath != "" {
268 fmt.Fprintf(b.w, "pkg: %s\n", b.importPath)
269 }
270 if cpu := sysinfo.CPU.Name(); cpu != "" {
271 fmt.Fprintf(b.w, "cpu: %s\n", cpu)
272 }
273 })
274 if b.context != nil {
275
276 b.context.processBench(b)
277 } else {
278
279 b.doBench()
280 }
281 }
282
283 func (b *B) doBench() BenchmarkResult {
284 go b.launch()
285 <-b.signal
286 return b.result
287 }
288
289
290
291
292
293 func (b *B) launch() {
294
295
296 defer func() {
297 b.signal <- true
298 }()
299
300
301 if b.benchTime.n > 0 {
302
303
304
305 if b.benchTime.n > 1 {
306 b.runN(b.benchTime.n)
307 }
308 } else {
309 d := b.benchTime.d
310 for n := int64(1); !b.failed && b.duration < d && n < 1e9; {
311 last := n
312
313 goalns := d.Nanoseconds()
314 prevIters := int64(b.N)
315 prevns := b.duration.Nanoseconds()
316 if prevns <= 0 {
317
318 prevns = 1
319 }
320
321
322
323
324
325 n = goalns * prevIters / prevns
326
327 n += n / 5
328
329 n = min(n, 100*last)
330
331 n = max(n, last+1)
332
333 n = min(n, 1e9)
334 b.runN(int(n))
335 }
336 }
337 b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes, b.extra}
338 }
339
340
341
342
343 func (b *B) Elapsed() time.Duration {
344 d := b.duration
345 if b.timerOn {
346 d += time.Since(b.start)
347 }
348 return d
349 }
350
351
352
353
354
355
356
357
358
359
360 func (b *B) ReportMetric(n float64, unit string) {
361 if unit == "" {
362 panic("metric unit must not be empty")
363 }
364 if strings.IndexFunc(unit, unicode.IsSpace) >= 0 {
365 panic("metric unit must not contain whitespace")
366 }
367 b.extra[unit] = n
368 }
369
370
371 type BenchmarkResult struct {
372 N int
373 T time.Duration
374 Bytes int64
375 MemAllocs uint64
376 MemBytes uint64
377
378
379 Extra map[string]float64
380 }
381
382
383 func (r BenchmarkResult) NsPerOp() int64 {
384 if v, ok := r.Extra["ns/op"]; ok {
385 return int64(v)
386 }
387 if r.N <= 0 {
388 return 0
389 }
390 return r.T.Nanoseconds() / int64(r.N)
391 }
392
393
394 func (r BenchmarkResult) mbPerSec() float64 {
395 if v, ok := r.Extra["MB/s"]; ok {
396 return v
397 }
398 if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 {
399 return 0
400 }
401 return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
402 }
403
404
405
406 func (r BenchmarkResult) AllocsPerOp() int64 {
407 if v, ok := r.Extra["allocs/op"]; ok {
408 return int64(v)
409 }
410 if r.N <= 0 {
411 return 0
412 }
413 return int64(r.MemAllocs) / int64(r.N)
414 }
415
416
417
418 func (r BenchmarkResult) AllocedBytesPerOp() int64 {
419 if v, ok := r.Extra["B/op"]; ok {
420 return int64(v)
421 }
422 if r.N <= 0 {
423 return 0
424 }
425 return int64(r.MemBytes) / int64(r.N)
426 }
427
428
429
430
431
432
433
434
435 func (r BenchmarkResult) String() string {
436 buf := new(strings.Builder)
437 fmt.Fprintf(buf, "%8d", r.N)
438
439
440 ns, ok := r.Extra["ns/op"]
441 if !ok {
442 ns = float64(r.T.Nanoseconds()) / float64(r.N)
443 }
444 if ns != 0 {
445 buf.WriteByte('\t')
446 prettyPrint(buf, ns, "ns/op")
447 }
448
449 if mbs := r.mbPerSec(); mbs != 0 {
450 fmt.Fprintf(buf, "\t%7.2f MB/s", mbs)
451 }
452
453
454
455 var extraKeys []string
456 for k := range r.Extra {
457 switch k {
458 case "ns/op", "MB/s", "B/op", "allocs/op":
459
460 continue
461 }
462 extraKeys = append(extraKeys, k)
463 }
464 sort.Strings(extraKeys)
465 for _, k := range extraKeys {
466 buf.WriteByte('\t')
467 prettyPrint(buf, r.Extra[k], k)
468 }
469 return buf.String()
470 }
471
472 func prettyPrint(w io.Writer, x float64, unit string) {
473
474
475
476
477 var format string
478 switch y := math.Abs(x); {
479 case y == 0 || y >= 999.95:
480 format = "%10.0f %s"
481 case y >= 99.995:
482 format = "%12.1f %s"
483 case y >= 9.9995:
484 format = "%13.2f %s"
485 case y >= 0.99995:
486 format = "%14.3f %s"
487 case y >= 0.099995:
488 format = "%15.4f %s"
489 case y >= 0.0099995:
490 format = "%16.5f %s"
491 case y >= 0.00099995:
492 format = "%17.6f %s"
493 default:
494 format = "%18.7f %s"
495 }
496 fmt.Fprintf(w, format, x, unit)
497 }
498
499
500 func (r BenchmarkResult) MemString() string {
501 return fmt.Sprintf("%8d B/op\t%8d allocs/op",
502 r.AllocedBytesPerOp(), r.AllocsPerOp())
503 }
504
505
506 func benchmarkName(name string, n int) string {
507 if n != 1 {
508 return fmt.Sprintf("%s-%d", name, n)
509 }
510 return name
511 }
512
513 type benchContext struct {
514 match *matcher
515
516 maxLen int
517 extLen int
518 }
519
520
521
522 func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) {
523 runBenchmarks("", matchString, benchmarks)
524 }
525
526 func runBenchmarks(importPath string, matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) bool {
527
528 if len(*matchBenchmarks) == 0 {
529 return true
530 }
531
532 maxprocs := 1
533 for _, procs := range cpuList {
534 if procs > maxprocs {
535 maxprocs = procs
536 }
537 }
538 ctx := &benchContext{
539 match: newMatcher(matchString, *matchBenchmarks, "-test.bench", *skip),
540 extLen: len(benchmarkName("", maxprocs)),
541 }
542 var bs []InternalBenchmark
543 for _, Benchmark := range benchmarks {
544 if _, matched, _ := ctx.match.fullName(nil, Benchmark.Name); matched {
545 bs = append(bs, Benchmark)
546 benchName := benchmarkName(Benchmark.Name, maxprocs)
547 if l := len(benchName) + ctx.extLen + 1; l > ctx.maxLen {
548 ctx.maxLen = l
549 }
550 }
551 }
552 main := &B{
553 common: common{
554 name: "Main",
555 w: os.Stdout,
556 bench: true,
557 },
558 importPath: importPath,
559 benchFunc: func(b *B) {
560 for _, Benchmark := range bs {
561 b.Run(Benchmark.Name, Benchmark.F)
562 }
563 },
564 benchTime: benchTime,
565 context: ctx,
566 }
567 if Verbose() {
568 main.chatty = newChattyPrinter(main.w)
569 }
570 main.runN(1)
571 return !main.failed
572 }
573
574
575 func (ctx *benchContext) processBench(b *B) {
576 for i, procs := range cpuList {
577 for j := uint(0); j < *count; j++ {
578 runtime.GOMAXPROCS(procs)
579 benchName := benchmarkName(b.name, procs)
580
581
582 if b.chatty == nil {
583 fmt.Fprintf(b.w, "%-*s\t", ctx.maxLen, benchName)
584 }
585
586 if i > 0 || j > 0 {
587 b = &B{
588 common: common{
589 signal: make(chan bool),
590 name: b.name,
591 w: b.w,
592 chatty: b.chatty,
593 bench: true,
594 },
595 benchFunc: b.benchFunc,
596 benchTime: b.benchTime,
597 }
598 b.run1()
599 }
600 r := b.doBench()
601 if b.failed {
602
603
604
605 fmt.Fprintf(b.w, "%s--- FAIL: %s\n%s", b.chatty.prefix(), benchName, b.output)
606 continue
607 }
608 results := r.String()
609 if b.chatty != nil {
610 fmt.Fprintf(b.w, "%-*s\t", ctx.maxLen, benchName)
611 }
612 if *benchmarkMemory || b.showAllocResult {
613 results += "\t" + r.MemString()
614 }
615 fmt.Fprintln(b.w, results)
616
617
618 if len(b.output) > 0 {
619 b.trimOutput()
620 fmt.Fprintf(b.w, "%s--- BENCH: %s\n%s", b.chatty.prefix(), benchName, b.output)
621 }
622 if p := runtime.GOMAXPROCS(-1); p != procs {
623 fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
624 }
625 if b.chatty != nil && b.chatty.json {
626 b.chatty.Updatef("", "=== NAME %s\n", "")
627 }
628 }
629 }
630 }
631
632
633
634
635 var hideStdoutForTesting = false
636
637
638
639
640
641
642 func (b *B) Run(name string, f func(b *B)) bool {
643
644
645 b.hasSub.Store(true)
646 benchmarkLock.Unlock()
647 defer benchmarkLock.Lock()
648
649 benchName, ok, partial := b.name, true, false
650 if b.context != nil {
651 benchName, ok, partial = b.context.match.fullName(&b.common, name)
652 }
653 if !ok {
654 return true
655 }
656 var pc [maxStackLen]uintptr
657 n := runtime.Callers(2, pc[:])
658 sub := &B{
659 common: common{
660 signal: make(chan bool),
661 name: benchName,
662 parent: &b.common,
663 level: b.level + 1,
664 creator: pc[:n],
665 w: b.w,
666 chatty: b.chatty,
667 bench: true,
668 },
669 importPath: b.importPath,
670 benchFunc: f,
671 benchTime: b.benchTime,
672 context: b.context,
673 }
674 if partial {
675
676
677 sub.hasSub.Store(true)
678 }
679
680 if b.chatty != nil {
681 labelsOnce.Do(func() {
682 fmt.Printf("goos: %s\n", runtime.GOOS)
683 fmt.Printf("goarch: %s\n", runtime.GOARCH)
684 if b.importPath != "" {
685 fmt.Printf("pkg: %s\n", b.importPath)
686 }
687 if cpu := sysinfo.CPU.Name(); cpu != "" {
688 fmt.Printf("cpu: %s\n", cpu)
689 }
690 })
691
692 if !hideStdoutForTesting {
693 if b.chatty.json {
694 b.chatty.Updatef(benchName, "=== RUN %s\n", benchName)
695 }
696 fmt.Println(benchName)
697 }
698 }
699
700 if sub.run1() {
701 sub.run()
702 }
703 b.add(sub.result)
704 return !sub.failed
705 }
706
707
708
709
710 func (b *B) add(other BenchmarkResult) {
711 r := &b.result
712
713
714 r.N = 1
715 r.T += time.Duration(other.NsPerOp())
716 if other.Bytes == 0 {
717
718
719 b.missingBytes = true
720 r.Bytes = 0
721 }
722 if !b.missingBytes {
723 r.Bytes += other.Bytes
724 }
725 r.MemAllocs += uint64(other.AllocsPerOp())
726 r.MemBytes += uint64(other.AllocedBytesPerOp())
727 }
728
729
730 func (b *B) trimOutput() {
731
732
733
734 const maxNewlines = 10
735 for nlCount, j := 0, 0; j < len(b.output); j++ {
736 if b.output[j] == '\n' {
737 nlCount++
738 if nlCount >= maxNewlines {
739 b.output = append(b.output[:j], "\n\t... [output truncated]\n"...)
740 break
741 }
742 }
743 }
744 }
745
746
747 type PB struct {
748 globalN *uint64
749 grain uint64
750 cache uint64
751 bN uint64
752 }
753
754
755 func (pb *PB) Next() bool {
756 if pb.cache == 0 {
757 n := atomic.AddUint64(pb.globalN, pb.grain)
758 if n <= pb.bN {
759 pb.cache = pb.grain
760 } else if n < pb.bN+pb.grain {
761 pb.cache = pb.bN + pb.grain - n
762 } else {
763 return false
764 }
765 }
766 pb.cache--
767 return true
768 }
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783 func (b *B) RunParallel(body func(*PB)) {
784 if b.N == 0 {
785 return
786 }
787
788
789
790 grain := uint64(0)
791 if b.previousN > 0 && b.previousDuration > 0 {
792 grain = 1e5 * uint64(b.previousN) / uint64(b.previousDuration)
793 }
794 if grain < 1 {
795 grain = 1
796 }
797
798
799 if grain > 1e4 {
800 grain = 1e4
801 }
802
803 n := uint64(0)
804 numProcs := b.parallelism * runtime.GOMAXPROCS(0)
805 var wg sync.WaitGroup
806 wg.Add(numProcs)
807 for p := 0; p < numProcs; p++ {
808 go func() {
809 defer wg.Done()
810 pb := &PB{
811 globalN: &n,
812 grain: grain,
813 bN: uint64(b.N),
814 }
815 body(pb)
816 }()
817 }
818 wg.Wait()
819 if n <= uint64(b.N) && !b.Failed() {
820 b.Fatal("RunParallel: body exited without pb.Next() == false")
821 }
822 }
823
824
825
826
827 func (b *B) SetParallelism(p int) {
828 if p >= 1 {
829 b.parallelism = p
830 }
831 }
832
833
834
835
836
837
838
839
840
841 func Benchmark(f func(b *B)) BenchmarkResult {
842 b := &B{
843 common: common{
844 signal: make(chan bool),
845 w: discard{},
846 },
847 benchFunc: f,
848 benchTime: benchTime,
849 }
850 if b.run1() {
851 b.run()
852 }
853 return b.result
854 }
855
856 type discard struct{}
857
858 func (discard) Write(b []byte) (n int, err error) { return len(b), nil }
859
View as plain text