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