golang.org/x/tools@v0.21.0/cmd/benchcmp/benchcmp.go (about)

     1  // Copyright 2014 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 main
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"os"
    11  	"sort"
    12  	"strconv"
    13  	"text/tabwriter"
    14  
    15  	"golang.org/x/tools/benchmark/parse"
    16  )
    17  
    18  var (
    19  	changedOnly = flag.Bool("changed", false, "show only benchmarks that have changed")
    20  	magSort     = flag.Bool("mag", false, "sort benchmarks by magnitude of change")
    21  	best        = flag.Bool("best", false, "compare best times from old and new")
    22  )
    23  
    24  const usageFooter = `
    25  Each input file should be from:
    26  	go test -run=NONE -bench=. > [old,new].txt
    27  
    28  Benchcmp compares old and new for each benchmark.
    29  
    30  If -test.benchmem=true is added to the "go test" command
    31  benchcmp will also compare memory allocations.
    32  `
    33  
    34  func main() {
    35  	fmt.Fprintf(os.Stderr, "benchcmp is deprecated in favor of benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat\n")
    36  	flag.Usage = func() {
    37  		fmt.Fprintf(os.Stderr, "usage: %s old.txt new.txt\n\n", os.Args[0])
    38  		flag.PrintDefaults()
    39  		fmt.Fprint(os.Stderr, usageFooter)
    40  		os.Exit(2)
    41  	}
    42  	flag.Parse()
    43  	if flag.NArg() != 2 {
    44  		flag.Usage()
    45  	}
    46  
    47  	before := parseFile(flag.Arg(0))
    48  	after := parseFile(flag.Arg(1))
    49  
    50  	cmps, warnings := Correlate(before, after)
    51  
    52  	for _, warn := range warnings {
    53  		fmt.Fprintln(os.Stderr, warn)
    54  	}
    55  
    56  	if len(cmps) == 0 {
    57  		fatal("benchcmp: no repeated benchmarks")
    58  	}
    59  
    60  	w := new(tabwriter.Writer)
    61  	w.Init(os.Stdout, 0, 0, 5, ' ', 0)
    62  	defer w.Flush()
    63  
    64  	var header bool // Has the header has been displayed yet for a given block?
    65  
    66  	if *magSort {
    67  		sort.Sort(ByDeltaNsPerOp(cmps))
    68  	} else {
    69  		sort.Sort(ByParseOrder(cmps))
    70  	}
    71  	for _, cmp := range cmps {
    72  		if !cmp.Measured(parse.NsPerOp) {
    73  			continue
    74  		}
    75  		if delta := cmp.DeltaNsPerOp(); !*changedOnly || delta.Changed() {
    76  			if !header {
    77  				fmt.Fprint(w, "benchmark\told ns/op\tnew ns/op\tdelta\n")
    78  				header = true
    79  			}
    80  			fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cmp.Name(), formatNs(cmp.Before.NsPerOp), formatNs(cmp.After.NsPerOp), delta.Percent())
    81  		}
    82  	}
    83  
    84  	header = false
    85  	if *magSort {
    86  		sort.Sort(ByDeltaMBPerS(cmps))
    87  	}
    88  	for _, cmp := range cmps {
    89  		if !cmp.Measured(parse.MBPerS) {
    90  			continue
    91  		}
    92  		if delta := cmp.DeltaMBPerS(); !*changedOnly || delta.Changed() {
    93  			if !header {
    94  				fmt.Fprint(w, "\nbenchmark\told MB/s\tnew MB/s\tspeedup\n")
    95  				header = true
    96  			}
    97  			fmt.Fprintf(w, "%s\t%.2f\t%.2f\t%s\n", cmp.Name(), cmp.Before.MBPerS, cmp.After.MBPerS, delta.Multiple())
    98  		}
    99  	}
   100  
   101  	header = false
   102  	if *magSort {
   103  		sort.Sort(ByDeltaAllocsPerOp(cmps))
   104  	}
   105  	for _, cmp := range cmps {
   106  		if !cmp.Measured(parse.AllocsPerOp) {
   107  			continue
   108  		}
   109  		if delta := cmp.DeltaAllocsPerOp(); !*changedOnly || delta.Changed() {
   110  			if !header {
   111  				fmt.Fprint(w, "\nbenchmark\told allocs\tnew allocs\tdelta\n")
   112  				header = true
   113  			}
   114  			fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocsPerOp, cmp.After.AllocsPerOp, delta.Percent())
   115  		}
   116  	}
   117  
   118  	header = false
   119  	if *magSort {
   120  		sort.Sort(ByDeltaAllocedBytesPerOp(cmps))
   121  	}
   122  	for _, cmp := range cmps {
   123  		if !cmp.Measured(parse.AllocedBytesPerOp) {
   124  			continue
   125  		}
   126  		if delta := cmp.DeltaAllocedBytesPerOp(); !*changedOnly || delta.Changed() {
   127  			if !header {
   128  				fmt.Fprint(w, "\nbenchmark\told bytes\tnew bytes\tdelta\n")
   129  				header = true
   130  			}
   131  			fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocedBytesPerOp, cmp.After.AllocedBytesPerOp, cmp.DeltaAllocedBytesPerOp().Percent())
   132  		}
   133  	}
   134  }
   135  
   136  func fatal(msg interface{}) {
   137  	fmt.Fprintln(os.Stderr, msg)
   138  	os.Exit(1)
   139  }
   140  
   141  func parseFile(path string) parse.Set {
   142  	f, err := os.Open(path)
   143  	if err != nil {
   144  		fatal(err)
   145  	}
   146  	defer f.Close()
   147  	bb, err := parse.ParseSet(f)
   148  	if err != nil {
   149  		fatal(err)
   150  	}
   151  	if *best {
   152  		selectBest(bb)
   153  	}
   154  	return bb
   155  }
   156  
   157  func selectBest(bs parse.Set) {
   158  	for name, bb := range bs {
   159  		if len(bb) < 2 {
   160  			continue
   161  		}
   162  		ord := bb[0].Ord
   163  		best := bb[0]
   164  		for _, b := range bb {
   165  			if b.NsPerOp < best.NsPerOp {
   166  				b.Ord = ord
   167  				best = b
   168  			}
   169  		}
   170  		bs[name] = []*parse.Benchmark{best}
   171  	}
   172  }
   173  
   174  // formatNs formats ns measurements to expose a useful amount of
   175  // precision. It mirrors the ns precision logic of testing.B.
   176  func formatNs(ns float64) string {
   177  	prec := 0
   178  	switch {
   179  	case ns < 10:
   180  		prec = 2
   181  	case ns < 100:
   182  		prec = 1
   183  	}
   184  	return strconv.FormatFloat(ns, 'f', prec, 64)
   185  }