Source file
src/testing/benchmark.go
Documentation: testing
1
2
3
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
23 var benchmarkLock sync.Mutex
24
25
26 var memStats runtime.MemStats
27
28
29
30 type InternalBenchmark struct {
31 Name string
32 F func(b *B)
33 }
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 type B struct {
49 common
50 importPath string
51 context *benchContext
52 N int
53 previousN int
54 previousDuration time.Duration
55 benchFunc func(b *B)
56 benchTime time.Duration
57 bytes int64
58 missingBytes bool
59 timerOn bool
60 showAllocResult bool
61 result BenchmarkResult
62 parallelism int
63
64 startAllocs uint64
65 startBytes uint64
66
67 netAllocs uint64
68 netBytes uint64
69 }
70
71
72
73
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
85
86
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
98
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
112
113 func (b *B) SetBytes(n int64) { b.bytes = n }
114
115
116
117
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
130 func (b *B) runN(n int) {
131 benchmarkLock.Lock()
132 defer benchmarkLock.Unlock()
133
134
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
166 func roundDown10(n int) int {
167 var tens = 0
168
169 for n >= 10 {
170 n = n / 10
171 tens++
172 }
173
174 result := 1
175 for i := 0; i < tens; i++ {
176 result *= 10
177 }
178 return result
179 }
180
181
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
199
200 func (b *B) run1() bool {
201 if ctx := b.context; ctx != nil {
202
203 if n := len(b.name) + ctx.extLen + 1; n > ctx.maxLen {
204 ctx.maxLen = n + 8
205 }
206 }
207 go func() {
208
209
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
222
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
240
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
251 b.context.processBench(b)
252 } else {
253
254 b.doBench()
255 }
256 }
257
258 func (b *B) doBench() BenchmarkResult {
259 go b.launch()
260 <-b.signal
261 return b.result
262 }
263
264
265
266
267
268 func (b *B) launch() {
269
270
271 defer func() {
272 b.signal <- true
273 }()
274
275
276 d := b.benchTime
277 for n := 1; !b.failed && b.duration < d && n < 1e9; {
278 last := n
279
280 n = int(d.Nanoseconds())
281 if nsop := b.nsPerOp(); nsop != 0 {
282 n /= int(nsop)
283 }
284
285
286
287 n = max(min(n+n/5, 100*last), last+1)
288
289 n = roundUp(n)
290 b.runN(n)
291 }
292 b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes}
293 }
294
295
296 type BenchmarkResult struct {
297 N int
298 T time.Duration
299 Bytes int64
300 MemAllocs uint64
301 MemBytes uint64
302 }
303
304 func (r BenchmarkResult) NsPerOp() int64 {
305 if r.N <= 0 {
306 return 0
307 }
308 return r.T.Nanoseconds() / int64(r.N)
309 }
310
311 func (r BenchmarkResult) mbPerSec() float64 {
312 if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 {
313 return 0
314 }
315 return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
316 }
317
318
319 func (r BenchmarkResult) AllocsPerOp() int64 {
320 if r.N <= 0 {
321 return 0
322 }
323 return int64(r.MemAllocs) / int64(r.N)
324 }
325
326
327 func (r BenchmarkResult) AllocedBytesPerOp() int64 {
328 if r.N <= 0 {
329 return 0
330 }
331 return int64(r.MemBytes) / int64(r.N)
332 }
333
334 func (r BenchmarkResult) String() string {
335 mbs := r.mbPerSec()
336 mb := ""
337 if mbs != 0 {
338 mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
339 }
340 nsop := r.NsPerOp()
341 ns := fmt.Sprintf("%10d ns/op", nsop)
342 if r.N > 0 && nsop < 100 {
343
344
345 if nsop < 10 {
346 ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
347 } else {
348 ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
349 }
350 }
351 return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb)
352 }
353
354
355 func (r BenchmarkResult) MemString() string {
356 return fmt.Sprintf("%8d B/op\t%8d allocs/op",
357 r.AllocedBytesPerOp(), r.AllocsPerOp())
358 }
359
360
361 func benchmarkName(name string, n int) string {
362 if n != 1 {
363 return fmt.Sprintf("%s-%d", name, n)
364 }
365 return name
366 }
367
368 type benchContext struct {
369 match *matcher
370
371 maxLen int
372 extLen int
373 }
374
375
376
377 func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) {
378 runBenchmarks("", matchString, benchmarks)
379 }
380
381 func runBenchmarks(importPath string, matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) bool {
382
383 if len(*matchBenchmarks) == 0 {
384 return true
385 }
386
387 maxprocs := 1
388 for _, procs := range cpuList {
389 if procs > maxprocs {
390 maxprocs = procs
391 }
392 }
393 ctx := &benchContext{
394 match: newMatcher(matchString, *matchBenchmarks, "-test.bench"),
395 extLen: len(benchmarkName("", maxprocs)),
396 }
397 var bs []InternalBenchmark
398 for _, Benchmark := range benchmarks {
399 if _, matched, _ := ctx.match.fullName(nil, Benchmark.Name); matched {
400 bs = append(bs, Benchmark)
401 benchName := benchmarkName(Benchmark.Name, maxprocs)
402 if l := len(benchName) + ctx.extLen + 1; l > ctx.maxLen {
403 ctx.maxLen = l
404 }
405 }
406 }
407 main := &B{
408 common: common{
409 name: "Main",
410 w: os.Stdout,
411 chatty: *chatty,
412 },
413 importPath: importPath,
414 benchFunc: func(b *B) {
415 for _, Benchmark := range bs {
416 b.Run(Benchmark.Name, Benchmark.F)
417 }
418 },
419 benchTime: *benchTime,
420 context: ctx,
421 }
422 main.runN(1)
423 return !main.failed
424 }
425
426
427 func (ctx *benchContext) processBench(b *B) {
428 for i, procs := range cpuList {
429 for j := uint(0); j < *count; j++ {
430 runtime.GOMAXPROCS(procs)
431 benchName := benchmarkName(b.name, procs)
432 fmt.Fprintf(b.w, "%-*s\t", ctx.maxLen, benchName)
433
434 if i > 0 || j > 0 {
435 b = &B{
436 common: common{
437 signal: make(chan bool),
438 name: b.name,
439 w: b.w,
440 chatty: b.chatty,
441 },
442 benchFunc: b.benchFunc,
443 benchTime: b.benchTime,
444 }
445 b.run1()
446 }
447 r := b.doBench()
448 if b.failed {
449
450
451
452 fmt.Fprintf(b.w, "--- FAIL: %s\n%s", benchName, b.output)
453 continue
454 }
455 results := r.String()
456 if *benchmarkMemory || b.showAllocResult {
457 results += "\t" + r.MemString()
458 }
459 fmt.Fprintln(b.w, results)
460
461
462 if len(b.output) > 0 {
463 b.trimOutput()
464 fmt.Fprintf(b.w, "--- BENCH: %s\n%s", benchName, b.output)
465 }
466 if p := runtime.GOMAXPROCS(-1); p != procs {
467 fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
468 }
469 }
470 }
471 }
472
473
474
475
476
477
478 func (b *B) Run(name string, f func(b *B)) bool {
479
480
481 atomic.StoreInt32(&b.hasSub, 1)
482 benchmarkLock.Unlock()
483 defer benchmarkLock.Lock()
484
485 benchName, ok, partial := b.name, true, false
486 if b.context != nil {
487 benchName, ok, partial = b.context.match.fullName(&b.common, name)
488 }
489 if !ok {
490 return true
491 }
492 var pc [maxStackLen]uintptr
493 n := runtime.Callers(2, pc[:])
494 sub := &B{
495 common: common{
496 signal: make(chan bool),
497 name: benchName,
498 parent: &b.common,
499 level: b.level + 1,
500 creator: pc[:n],
501 w: b.w,
502 chatty: b.chatty,
503 },
504 importPath: b.importPath,
505 benchFunc: f,
506 benchTime: b.benchTime,
507 context: b.context,
508 }
509 if partial {
510
511
512 atomic.StoreInt32(&sub.hasSub, 1)
513 }
514 if sub.run1() {
515 sub.run()
516 }
517 b.add(sub.result)
518 return !sub.failed
519 }
520
521
522
523
524 func (b *B) add(other BenchmarkResult) {
525 r := &b.result
526
527
528 r.N = 1
529 r.T += time.Duration(other.NsPerOp())
530 if other.Bytes == 0 {
531
532
533 b.missingBytes = true
534 r.Bytes = 0
535 }
536 if !b.missingBytes {
537 r.Bytes += other.Bytes
538 }
539 r.MemAllocs += uint64(other.AllocsPerOp())
540 r.MemBytes += uint64(other.AllocedBytesPerOp())
541 }
542
543
544 func (b *B) trimOutput() {
545
546
547
548 const maxNewlines = 10
549 for nlCount, j := 0, 0; j < len(b.output); j++ {
550 if b.output[j] == '\n' {
551 nlCount++
552 if nlCount >= maxNewlines {
553 b.output = append(b.output[:j], "\n\t... [output truncated]\n"...)
554 break
555 }
556 }
557 }
558 }
559
560
561 type PB struct {
562 globalN *uint64
563 grain uint64
564 cache uint64
565 bN uint64
566 }
567
568
569 func (pb *PB) Next() bool {
570 if pb.cache == 0 {
571 n := atomic.AddUint64(pb.globalN, pb.grain)
572 if n <= pb.bN {
573 pb.cache = pb.grain
574 } else if n < pb.bN+pb.grain {
575 pb.cache = pb.bN + pb.grain - n
576 } else {
577 return false
578 }
579 }
580 pb.cache--
581 return true
582 }
583
584
585
586
587
588
589
590
591
592
593
594 func (b *B) RunParallel(body func(*PB)) {
595 if b.N == 0 {
596 return
597 }
598
599
600
601 grain := uint64(0)
602 if b.previousN > 0 && b.previousDuration > 0 {
603 grain = 1e5 * uint64(b.previousN) / uint64(b.previousDuration)
604 }
605 if grain < 1 {
606 grain = 1
607 }
608
609
610 if grain > 1e4 {
611 grain = 1e4
612 }
613
614 n := uint64(0)
615 numProcs := b.parallelism * runtime.GOMAXPROCS(0)
616 var wg sync.WaitGroup
617 wg.Add(numProcs)
618 for p := 0; p < numProcs; p++ {
619 go func() {
620 defer wg.Done()
621 pb := &PB{
622 globalN: &n,
623 grain: grain,
624 bN: uint64(b.N),
625 }
626 body(pb)
627 }()
628 }
629 wg.Wait()
630 if n <= uint64(b.N) && !b.Failed() {
631 b.Fatal("RunParallel: body exited without pb.Next() == false")
632 }
633 }
634
635
636
637
638 func (b *B) SetParallelism(p int) {
639 if p >= 1 {
640 b.parallelism = p
641 }
642 }
643
644
645
646
647
648
649 func Benchmark(f func(b *B)) BenchmarkResult {
650 b := &B{
651 common: common{
652 signal: make(chan bool),
653 w: discard{},
654 },
655 benchFunc: f,
656 benchTime: *benchTime,
657 }
658 if b.run1() {
659 b.run()
660 }
661 return b.result
662 }
663
664 type discard struct{}
665
666 func (discard) Write(b []byte) (n int, err error) { return len(b), nil }
667
View as plain text