1
2
3
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
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
175
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 }
186
View as plain text