blob: 0925b44ff9103abc5de1d43d0de5084f2c8864b2 [file] [log] [blame]
Nigel Tao9e4c87b2021-03-12 23:56:43 +11001// Copyright 2021 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// +build ignore
16
17package main
18
19// benchstat-ratio.go prints the relative ratios of similarly named benchstat
20// summary lines. The benchstat tool (written in Go) is not specific to Wuffs.
21// It is documented at https://godoc.org/golang.org/x/perf/cmd/benchstat
22//
23// Out of the box, benchstat (when passed two filename arguments) compares
24// *identically* named lines (i.e. before and after a code change). This
25// program computes ratios for *different-but-similar* named lines (i.e.
26// multiple implementations or compilers for the same algorithm or test data),
27// grouped by the name fragment after the first '_' and before the first '/'.
28//
29// By default, the baseline case (normalized to a ratio of 1.00x) is the one
30// whose name starts with "mimic_" and contains "/gcc".
31//
32// For example, given benchstat output like:
33//
34// wuffs_crc32_ieee_10k/clang9 8.32GB/s ± 4%
35// wuffs_crc32_ieee_100k/clang9 10.8GB/s ± 1%
36// mimic_crc32_ieee_10k/clang9 771MB/s ± 0%
37// mimic_crc32_ieee_100k/clang9 772MB/s ± 0%
38// wuffs_crc32_ieee_10k/gcc10 8.68GB/s ± 0%
39// wuffs_crc32_ieee_100k/gcc10 10.9GB/s ± 0%
40// mimic_crc32_ieee_10k/gcc10 775MB/s ± 0%
41// mimic_crc32_ieee_100k/gcc10 773MB/s ± 0%
42//
43// Piping that through this program prints:
44//
45// wuffs_crc32_ieee_10k/clang9 8.32GB/s ± 4% 10.74x
46// wuffs_crc32_ieee_100k/clang9 10.8GB/s ± 1% 13.97x
47// mimic_crc32_ieee_10k/clang9 771MB/s ± 0% 0.99x
48// mimic_crc32_ieee_100k/clang9 772MB/s ± 0% 1.00x
49// wuffs_crc32_ieee_10k/gcc10 8.68GB/s ± 0% 11.20x
50// wuffs_crc32_ieee_100k/gcc10 10.9GB/s ± 0% 14.10x
51// mimic_crc32_ieee_10k/gcc10 775MB/s ± 0% 1.00x
52// mimic_crc32_ieee_100k/gcc10 773MB/s ± 0% 1.00x
53//
54// There are two groups here: "crc32_ieee_10k" and "crc32_ieee_100k".
55//
56// Usage: benchstat etc | go run benchstat-ratio.go
57
58import (
59 "bufio"
60 "flag"
61 "fmt"
62 "os"
63 "strconv"
64 "strings"
65)
66
67var (
68 baseline = flag.String("baseline", "mimic", "benchmark name prefix of the 1.00x case")
69)
70
71func main() {
72 if err := main1(); err != nil {
73 os.Stderr.WriteString(err.Error() + "\n")
74 os.Exit(1)
75 }
76}
77
78func main1() error {
79 flag.Parse()
80 baselineUnderscore = *baseline + "_"
81
82 r := bufio.NewScanner(os.Stdin)
83 for r.Scan() {
84 line := strings.TrimSpace(string(r.Bytes()))
85 pmIndex := strings.Index(line, plusMinus)
86 if pmIndex < 0 {
87 // Collapse multiple blank lines to a single one.
88 if (len(entries) > 0) && (entries[len(entries)-1].key != "") {
89 entries = append(entries, entry{})
90 }
91 continue
92 }
93
94 // Find the "/gcc10" in "mimic_etc/gcc10".
95 if (gccSuffix == "") && strings.HasPrefix(line, baselineUnderscore) {
96 if i := strings.Index(line, slashGCC); i >= 0 {
97 gccSuffix = line[i:]
98 if j := strings.IndexByte(gccSuffix, ' '); j >= 0 {
99 gccSuffix = gccSuffix[:j]
100 }
101 }
102 }
103
104 key, val := parse(line[:pmIndex])
105 if key == "" {
106 return fmt.Errorf("could not parse %q", line)
107 } else if _, ok := values[key]; ok {
108 return fmt.Errorf("duplicate key for %q", line)
109 }
110 entries = append(entries, entry{key, line})
111 values[key] = val
112 }
113 if err := r.Err(); err != nil {
114 return err
115 }
116
117 for _, e := range entries {
118 if e.key == "" {
119 fmt.Println()
120 continue
121 }
122 eVal, eOK := values[e.key]
123 bVal, bOK := values[baselineKeyFor(e.key)]
124 if eOK && bOK {
125 switch e.key[0] {
126 case '1':
127 fmt.Printf("%s %6.2fx\n", e.line, bVal/eVal)
128 continue
129 case '2':
130 fmt.Printf("%s %6.2fx\n", e.line, eVal/bVal)
131 continue
132 }
133 }
134 fmt.Printf("%s\n", e.line)
135 }
136
137 return nil
138}
139
140func parse(line string) (key string, val float64) {
141 i := strings.IndexByte(line, ' ')
142 if i < 0 {
143 return "", 0
144 }
145 k, v := line[:i], strings.TrimSpace(line[i+1:])
146
147 prefix, multiplier := "", 1e0
148 switch {
149 case strings.HasSuffix(v, suffix_GBs):
150 prefix, multiplier = prefix2, 1e9
151 v = v[:len(v)-len(suffix_GBs)]
152 case strings.HasSuffix(v, suffix_MBs):
153 prefix, multiplier = prefix2, 1e6
154 v = v[:len(v)-len(suffix_MBs)]
155 case strings.HasSuffix(v, suffix_KBs):
156 prefix, multiplier = prefix2, 1e3
157 v = v[:len(v)-len(suffix_KBs)]
158 case strings.HasSuffix(v, suffix__Bs):
159 prefix, multiplier = prefix2, 1e0
160 v = v[:len(v)-len(suffix__Bs)]
161
162 case strings.HasSuffix(v, suffix_ns):
163 prefix, multiplier = prefix1, 1e0
164 v = v[:len(v)-len(suffix_ns)]
165 case strings.HasSuffix(v, suffix_µs):
166 prefix, multiplier = prefix1, 1e3
167 v = v[:len(v)-len(suffix_µs)]
168 case strings.HasSuffix(v, suffix_ms):
169 prefix, multiplier = prefix1, 1e6
170 v = v[:len(v)-len(suffix_ms)]
171 case strings.HasSuffix(v, suffix__s):
172 prefix, multiplier = prefix1, 1e9
173 v = v[:len(v)-len(suffix__s)]
174 default:
175 return "", 0
176 }
177
178 x, err := strconv.ParseFloat(v, 64)
179 if err != nil {
180 return "", 0
181 }
182 return prefix + string(k), x * multiplier
183}
184
185func baselineKeyFor(key string) (bKey string) {
186 if key == "" {
187 return ""
188 }
189 i := strings.IndexByte(key, '_')
190 if i < 0 {
191 return ""
192 }
193 j := strings.IndexByte(key[i:], '/')
194 if j < 0 {
Nigel Tao85bdcd12021-03-13 22:40:37 +1100195 j = len(key) - i
Nigel Tao9e4c87b2021-03-12 23:56:43 +1100196 }
197 return key[:1] + *baseline + key[i:i+j] + gccSuffix
198}
199
200type entry struct {
201 key string
202 line string
203}
204
205var (
206 entries []entry
207 values = map[string]float64{}
208
209 baselineUnderscore string
210 gccSuffix string
211)
212
213const (
214 plusMinus = " ± "
215 slashGCC = "/gcc"
216
217 prefix1 = "1" // val is nanoseconds.
218 prefix2 = "2" // val is bytes per second.
219
220 suffix_GBs = "GB/s"
221 suffix_MBs = "MB/s"
222 suffix_KBs = "KB/s"
223 suffix__Bs = "B/s"
224
225 suffix_ns = "ns"
226 suffix_µs = "µs"
227 suffix_ms = "ms"
228 suffix__s = "s"
229)