blob: 780e422a667a09ec320c46ce0650fee17317f852 [file] [log] [blame]
Russ Coxa7a7b5b2017-01-27 12:19:00 -05001// Copyright 2017 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
Russ Coxc1e5ad72017-01-27 16:34:00 -05005package benchstat
Russ Coxa7a7b5b2017-01-27 12:19:00 -05006
7import (
8 "fmt"
9
10 "golang.org/x/perf/internal/stats"
11)
12
13// A Table is a table for display in the benchstat output.
14type Table struct {
Russ Cox1b4493f2017-01-27 14:35:35 -050015 Metric string
16 OldNewDelta bool // is this an old-new-delta table?
17 Configs []string
18 Rows []*Row
Russ Coxa7a7b5b2017-01-27 12:19:00 -050019}
20
21// A Row is a table row for display in the benchstat output.
22type Row struct {
23 Benchmark string // benchmark name
24 Scaler Scaler // formatter for stats means
Russ Cox1b4493f2017-01-27 14:35:35 -050025 Metrics []*Metrics // columns of statistics
Russ Coxa7a7b5b2017-01-27 12:19:00 -050026 Delta string // formatted percent change
27 Note string // additional information
Russ Coxfc59af52017-01-27 15:10:46 -050028 Change int // +1 better, -1 worse, 0 unchanged
Russ Coxa7a7b5b2017-01-27 12:19:00 -050029}
30
31// Tables returns tables comparing the benchmarks in the collection.
Russ Coxc1e5ad72017-01-27 16:34:00 -050032func (c *Collection) Tables() []*Table {
33 deltaTest := c.DeltaTest
34 if deltaTest == nil {
35 deltaTest = UTest
36 }
37 alpha := c.Alpha
38 if alpha == 0 {
39 alpha = 0.05
40 }
41
Russ Coxa7a7b5b2017-01-27 12:19:00 -050042 // Update statistics.
43 for _, m := range c.Metrics {
44 m.computeStats()
45 }
46
47 var tables []*Table
48 key := Key{}
49 for _, key.Unit = range c.Units {
50 table := new(Table)
51 table.Configs = c.Configs
52 table.Metric = metricOf(key.Unit)
Russ Cox1b4493f2017-01-27 14:35:35 -050053 table.OldNewDelta = len(c.Configs) == 2
Russ Coxa7a7b5b2017-01-27 12:19:00 -050054 for _, key.Benchmark = range c.Benchmarks {
55 row := &Row{Benchmark: key.Benchmark}
56
57 for _, key.Config = range c.Configs {
58 m := c.Metrics[key]
Russ Coxa7a7b5b2017-01-27 12:19:00 -050059 if m == nil {
Russ Cox1b4493f2017-01-27 14:35:35 -050060 row.Metrics = append(row.Metrics, new(Metrics))
Russ Coxa7a7b5b2017-01-27 12:19:00 -050061 continue
62 }
Russ Cox1b4493f2017-01-27 14:35:35 -050063 row.Metrics = append(row.Metrics, m)
Russ Coxa7a7b5b2017-01-27 12:19:00 -050064 if row.Scaler == nil {
65 row.Scaler = NewScaler(m.Mean, m.Unit)
66 }
67 }
68
69 // If there are only two configs being compared, add stats.
Russ Cox1b4493f2017-01-27 14:35:35 -050070 if table.OldNewDelta {
Russ Coxa7a7b5b2017-01-27 12:19:00 -050071 k0 := key
72 k0.Config = c.Configs[0]
73 k1 := key
74 k1.Config = c.Configs[1]
75 old := c.Metrics[k0]
76 new := c.Metrics[k1]
Russ Cox1b4493f2017-01-27 14:35:35 -050077 // If one is missing, omit row entirely.
78 // TODO: Control this better.
Russ Cox0210f852017-01-27 16:39:10 -050079 if old == nil || new == nil {
Russ Coxa7a7b5b2017-01-27 12:19:00 -050080 continue
81 }
82 pval, testerr := deltaTest(old, new)
83 row.Delta = "~"
84 if testerr == stats.ErrZeroVariance {
85 row.Note = "(zero variance)"
86 } else if testerr == stats.ErrSampleSize {
87 row.Note = "(too few samples)"
88 } else if testerr == stats.ErrSamplesEqual {
89 row.Note = "(all equal)"
90 } else if testerr != nil {
91 row.Note = fmt.Sprintf("(%s)", testerr)
Russ Coxc1e5ad72017-01-27 16:34:00 -050092 } else if pval < alpha {
Russ Coxfc59af52017-01-27 15:10:46 -050093 pct := ((new.Mean / old.Mean) - 1.0) * 100.0
94 row.Delta = fmt.Sprintf("%+.2f%%", pct)
95 if pct < 0 == (table.Metric != "speed") { // smaller is better, except speeds
96 row.Change = +1
97 } else {
98 row.Change = -1
99 }
Russ Coxa7a7b5b2017-01-27 12:19:00 -0500100 }
101 if row.Note == "" && pval != -1 {
102 row.Note = fmt.Sprintf("(p=%0.3f n=%d+%d)", pval, len(old.RValues), len(new.RValues))
103 }
104 }
105
106 table.Rows = append(table.Rows, row)
107 }
108
109 if len(table.Rows) > 0 {
Russ Coxc1e5ad72017-01-27 16:34:00 -0500110 if c.AddGeoMean {
Russ Cox1b4493f2017-01-27 14:35:35 -0500111 addGeomean(c, table, key.Unit, table.OldNewDelta)
Russ Coxa7a7b5b2017-01-27 12:19:00 -0500112 }
113 tables = append(tables, table)
114 }
115 }
116
117 return tables
118}
119
120// metricOf returns the name of the metric with the given unit.
121func metricOf(unit string) string {
122 switch unit {
123 case "ns/op":
124 return "time/op"
125 case "B/op":
126 return "alloc/op"
127 case "MB/s":
128 return "speed"
129 default:
130 return unit
131 }
132}
133
134// addGeomean adds a "geomean" row to the table,
135// showing the geometric mean of all the benchmarks.
136func addGeomean(c *Collection, t *Table, unit string, delta bool) {
137 row := &Row{Benchmark: "[Geo mean]"}
138 key := Key{Unit: unit}
139 geomeans := []float64{}
140 for _, key.Config = range c.Configs {
141 var means []float64
142 for _, key.Benchmark = range c.Benchmarks {
143 m := c.Metrics[key]
144 if m != nil {
145 means = append(means, m.Mean)
146 }
147 }
148 if len(means) == 0 {
Russ Cox1b4493f2017-01-27 14:35:35 -0500149 row.Metrics = append(row.Metrics, new(Metrics))
Russ Coxa7a7b5b2017-01-27 12:19:00 -0500150 delta = false
151 } else {
152 geomean := stats.GeoMean(means)
153 geomeans = append(geomeans, geomean)
154 if row.Scaler == nil {
155 row.Scaler = NewScaler(geomean, unit)
156 }
157 row.Metrics = append(row.Metrics, &Metrics{
158 Unit: unit,
159 Mean: geomean,
160 })
161 }
162 }
163 if delta {
164 row.Delta = fmt.Sprintf("%+.2f%%", ((geomeans[1]/geomeans[0])-1.0)*100.0)
165 }
166 t.Rows = append(t.Rows, row)
167}