github.com/april1989/origin-go-tools@v0.0.32/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 "github.com/april1989/origin-go-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 golibexec_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 }