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 | } |
186 |
Members