1
0
mirror of https://github.com/golang/go synced 2024-11-18 13:34:41 -07:00

benchmark/parse, cmd/benchcmp: new package for parsing benchmarks

Move the parser for benchmark output from cmd/benchcmp into its own
package, benchmark/parse.

The majority of the change is just moving code around. Instead of
implementing the '-best' flag in ParseBenchSet, it is now implemented in
its own function 'selectBest' in cmd/benchcmp.

Bench.Ord (the ordinal position of a Bench within a BenchSet) has been
exported.

Change-Id: Id27032a220f9ff2596117b58b86243998695a804
Reviewed-on: https://go-review.googlesource.com/2102
Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
Dominik Honnef 2014-12-24 02:15:25 +01:00 committed by Rob Pike
parent ac848a9536
commit 0ff6678340
6 changed files with 124 additions and 84 deletions

View File

@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package main // Package parse provides support for parsing benchmark results as
// generated by 'go test -bench'.
package parse // import "golang.org/x/tools/benchmark/parse"
import ( import (
"bufio" "bufio"
@ -31,7 +33,7 @@ type Bench struct {
BOp uint64 // bytes allocated per iteration BOp uint64 // bytes allocated per iteration
AllocsOp uint64 // allocs per iteration AllocsOp uint64 // allocs per iteration
Measured int // which measurements were recorded Measured int // which measurements were recorded
ord int // ordinal position within a benchmark run, used for sorting Ord int // ordinal position within a benchmark run
} }
// ParseLine extracts a Bench from a single line of testing.B output. // ParseLine extracts a Bench from a single line of testing.B output.
@ -105,24 +107,17 @@ func (b *Bench) String() string {
// testing.B run, keyed by name to facilitate comparison. // testing.B run, keyed by name to facilitate comparison.
type BenchSet map[string][]*Bench type BenchSet map[string][]*Bench
// Parse extracts a BenchSet from testing.B output. Parse // ParseBenchSet extracts a BenchSet from testing.B output.
// preserves the order of benchmarks that have identical names. // ParseBenchSet preserves the order of benchmarks that have identical
// names.
func ParseBenchSet(r io.Reader) (BenchSet, error) { func ParseBenchSet(r io.Reader) (BenchSet, error) {
bb := make(BenchSet) bb := make(BenchSet)
scan := bufio.NewScanner(r) scan := bufio.NewScanner(r)
ord := 0 ord := 0
for scan.Scan() { for scan.Scan() {
if b, err := ParseLine(scan.Text()); err == nil { if b, err := ParseLine(scan.Text()); err == nil {
b.ord = ord b.Ord = ord
ord++ ord++
old := bb[b.Name]
if *best && old != nil {
if old[0].NsOp < b.NsOp {
continue
}
b.ord = old[0].ord
bb[b.Name] = old[:0]
}
bb[b.Name] = append(bb[b.Name], b) bb[b.Name] = append(bb[b.Name], b)
} }
} }

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package main package parse
import ( import (
"reflect" "reflect"
@ -117,7 +117,7 @@ func TestParseBenchSet(t *testing.T) {
Name: "BenchmarkReadRequestApachebench", Name: "BenchmarkReadRequestApachebench",
N: 1000000, NsOp: 2960, MbS: 27.70, BOp: 839, AllocsOp: 9, N: 1000000, NsOp: 2960, MbS: 27.70, BOp: 839, AllocsOp: 9,
Measured: NsOp | MbS | BOp | AllocsOp, Measured: NsOp | MbS | BOp | AllocsOp,
ord: 2, Ord: 2,
}, },
}, },
"BenchmarkClientServerParallel64": []*Bench{ "BenchmarkClientServerParallel64": []*Bench{
@ -125,7 +125,7 @@ func TestParseBenchSet(t *testing.T) {
Name: "BenchmarkClientServerParallel64", Name: "BenchmarkClientServerParallel64",
N: 50000, NsOp: 59192, BOp: 7028, AllocsOp: 60, N: 50000, NsOp: 59192, BOp: 7028, AllocsOp: 60,
Measured: NsOp | BOp | AllocsOp, Measured: NsOp | BOp | AllocsOp,
ord: 3, Ord: 3,
}, },
}, },
"BenchmarkEncrypt": []*Bench{ "BenchmarkEncrypt": []*Bench{
@ -133,53 +133,13 @@ func TestParseBenchSet(t *testing.T) {
Name: "BenchmarkEncrypt", Name: "BenchmarkEncrypt",
N: 100000000, NsOp: 19.6, N: 100000000, NsOp: 19.6,
Measured: NsOp, Measured: NsOp,
ord: 0, Ord: 0,
}, },
{ {
Name: "BenchmarkEncrypt", Name: "BenchmarkEncrypt",
N: 5000000, NsOp: 517, N: 5000000, NsOp: 517,
Measured: NsOp, Measured: NsOp,
ord: 1, Ord: 1,
},
},
}
have, err := ParseBenchSet(strings.NewReader(in))
if err != nil {
t.Fatalf("unexpected err during ParseBenchSet: %v", err)
}
if !reflect.DeepEqual(want, have) {
t.Errorf("parsed bench set incorrectly, want %v have %v", want, have)
}
}
func TestParseBenchSetBest(t *testing.T) {
// Test that -best mode takes best ns/op.
*best = true
defer func() {
*best = false
}()
in := `
Benchmark1 10 100 ns/op
Benchmark2 10 60 ns/op
Benchmark2 10 500 ns/op
Benchmark1 10 50 ns/op
`
want := BenchSet{
"Benchmark1": []*Bench{
{
Name: "Benchmark1",
N: 10, NsOp: 50, Measured: NsOp,
ord: 0,
},
},
"Benchmark2": []*Bench{
{
Name: "Benchmark2",
N: 10, NsOp: 60, Measured: NsOp,
ord: 1,
}, },
}, },
} }

View File

@ -11,6 +11,8 @@ import (
"sort" "sort"
"strconv" "strconv"
"text/tabwriter" "text/tabwriter"
"golang.org/x/tools/benchmark/parse"
) )
var ( var (
@ -66,7 +68,7 @@ func main() {
sort.Sort(ByParseOrder(cmps)) sort.Sort(ByParseOrder(cmps))
} }
for _, cmp := range cmps { for _, cmp := range cmps {
if !cmp.Measured(NsOp) { if !cmp.Measured(parse.NsOp) {
continue continue
} }
if delta := cmp.DeltaNsOp(); !*changedOnly || delta.Changed() { if delta := cmp.DeltaNsOp(); !*changedOnly || delta.Changed() {
@ -83,7 +85,7 @@ func main() {
sort.Sort(ByDeltaMbS(cmps)) sort.Sort(ByDeltaMbS(cmps))
} }
for _, cmp := range cmps { for _, cmp := range cmps {
if !cmp.Measured(MbS) { if !cmp.Measured(parse.MbS) {
continue continue
} }
if delta := cmp.DeltaMbS(); !*changedOnly || delta.Changed() { if delta := cmp.DeltaMbS(); !*changedOnly || delta.Changed() {
@ -100,7 +102,7 @@ func main() {
sort.Sort(ByDeltaAllocsOp(cmps)) sort.Sort(ByDeltaAllocsOp(cmps))
} }
for _, cmp := range cmps { for _, cmp := range cmps {
if !cmp.Measured(AllocsOp) { if !cmp.Measured(parse.AllocsOp) {
continue continue
} }
if delta := cmp.DeltaAllocsOp(); !*changedOnly || delta.Changed() { if delta := cmp.DeltaAllocsOp(); !*changedOnly || delta.Changed() {
@ -117,7 +119,7 @@ func main() {
sort.Sort(ByDeltaBOp(cmps)) sort.Sort(ByDeltaBOp(cmps))
} }
for _, cmp := range cmps { for _, cmp := range cmps {
if !cmp.Measured(BOp) { if !cmp.Measured(parse.BOp) {
continue continue
} }
if delta := cmp.DeltaBOp(); !*changedOnly || delta.Changed() { if delta := cmp.DeltaBOp(); !*changedOnly || delta.Changed() {
@ -135,18 +137,38 @@ func fatal(msg interface{}) {
os.Exit(1) os.Exit(1)
} }
func parseFile(path string) BenchSet { func parseFile(path string) parse.BenchSet {
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
fatal(err) fatal(err)
} }
bb, err := ParseBenchSet(f) bb, err := parse.ParseBenchSet(f)
if err != nil { if err != nil {
fatal(err) fatal(err)
} }
if *best {
selectBest(bb)
}
return bb return bb
} }
func selectBest(bs parse.BenchSet) {
for name, bb := range bs {
if len(bb) < 2 {
continue
}
ord := bb[0].Ord
best := bb[0]
for _, b := range bb {
if b.NsOp < best.NsOp {
b.Ord = ord
best = b
}
}
bs[name] = []*parse.Bench{best}
}
}
// formatNs formats ns measurements to expose a useful amount of // formatNs formats ns measurements to expose a useful amount of
// precision. It mirrors the ns precision logic of testing.B. // precision. It mirrors the ns precision logic of testing.B.
func formatNs(ns float64) string { func formatNs(ns float64) string {

View File

@ -0,0 +1,59 @@
package main
import (
"reflect"
"testing"
"golang.org/x/tools/benchmark/parse"
)
func TestSelectBest(t *testing.T) {
have := parse.BenchSet{
"Benchmark1": []*parse.Bench{
{
Name: "Benchmark1",
N: 10, NsOp: 100, Measured: parse.NsOp,
Ord: 0,
},
{
Name: "Benchmark1",
N: 10, NsOp: 50, Measured: parse.NsOp,
Ord: 3,
},
},
"Benchmark2": []*parse.Bench{
{
Name: "Benchmark2",
N: 10, NsOp: 60, Measured: parse.NsOp,
Ord: 1,
},
{
Name: "Benchmark2",
N: 10, NsOp: 500, Measured: parse.NsOp,
Ord: 2,
},
},
}
want := parse.BenchSet{
"Benchmark1": []*parse.Bench{
{
Name: "Benchmark1",
N: 10, NsOp: 50, Measured: parse.NsOp,
Ord: 0,
},
},
"Benchmark2": []*parse.Bench{
{
Name: "Benchmark2",
N: 10, NsOp: 60, Measured: parse.NsOp,
Ord: 1,
},
},
}
selectBest(have)
if !reflect.DeepEqual(want, have) {
t.Errorf("filtered bench set incorrectly, want %v have %v", want, have)
}
}

View File

@ -7,16 +7,18 @@ package main
import ( import (
"fmt" "fmt"
"math" "math"
"golang.org/x/tools/benchmark/parse"
) )
// BenchCmp is a pair of benchmarks. // BenchCmp is a pair of benchmarks.
type BenchCmp struct { type BenchCmp struct {
Before *Bench Before *parse.Bench
After *Bench After *parse.Bench
} }
// Correlate correlates benchmarks from two BenchSets. // Correlate correlates benchmarks from two BenchSets.
func Correlate(before, after BenchSet) (cmps []BenchCmp, warnings []string) { func Correlate(before, after parse.BenchSet) (cmps []BenchCmp, warnings []string) {
cmps = make([]BenchCmp, 0, len(after)) cmps = make([]BenchCmp, 0, len(after))
for name, beforebb := range before { for name, beforebb := range before {
afterbb := after[name] afterbb := after[name]
@ -102,7 +104,7 @@ type ByParseOrder []BenchCmp
func (x ByParseOrder) Len() int { return len(x) } func (x ByParseOrder) Len() int { return len(x) }
func (x ByParseOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] } func (x ByParseOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByParseOrder) Less(i, j int) bool { return x[i].Before.ord < x[j].Before.ord } func (x ByParseOrder) Less(i, j int) bool { return x[i].Before.Ord < x[j].Before.Ord }
// lessByDelta provides lexicographic ordering: // lessByDelta provides lexicographic ordering:
// * largest delta by magnitude // * largest delta by magnitude

View File

@ -9,6 +9,8 @@ import (
"reflect" "reflect"
"sort" "sort"
"testing" "testing"
"golang.org/x/tools/benchmark/parse"
) )
func TestDelta(t *testing.T) { func TestDelta(t *testing.T) {
@ -52,29 +54,29 @@ func TestCorrelate(t *testing.T) {
// Benches that are going to be successfully correlated get N thus: // Benches that are going to be successfully correlated get N thus:
// 0x<counter><num benches><b = before | a = after> // 0x<counter><num benches><b = before | a = after>
// Read this: "<counter> of <num benches>, from <before|after>". // Read this: "<counter> of <num benches>, from <before|after>".
before := BenchSet{ before := parse.BenchSet{
"BenchmarkOneEach": []*Bench{{Name: "BenchmarkOneEach", N: 0x11b}}, "BenchmarkOneEach": []*parse.Bench{{Name: "BenchmarkOneEach", N: 0x11b}},
"BenchmarkOneToNone": []*Bench{{Name: "BenchmarkOneToNone"}}, "BenchmarkOneToNone": []*parse.Bench{{Name: "BenchmarkOneToNone"}},
"BenchmarkOneToTwo": []*Bench{{Name: "BenchmarkOneToTwo"}}, "BenchmarkOneToTwo": []*parse.Bench{{Name: "BenchmarkOneToTwo"}},
"BenchmarkTwoToOne": []*Bench{ "BenchmarkTwoToOne": []*parse.Bench{
{Name: "BenchmarkTwoToOne"}, {Name: "BenchmarkTwoToOne"},
{Name: "BenchmarkTwoToOne"}, {Name: "BenchmarkTwoToOne"},
}, },
"BenchmarkTwoEach": []*Bench{ "BenchmarkTwoEach": []*parse.Bench{
{Name: "BenchmarkTwoEach", N: 0x12b}, {Name: "BenchmarkTwoEach", N: 0x12b},
{Name: "BenchmarkTwoEach", N: 0x22b}, {Name: "BenchmarkTwoEach", N: 0x22b},
}, },
} }
after := BenchSet{ after := parse.BenchSet{
"BenchmarkOneEach": []*Bench{{Name: "BenchmarkOneEach", N: 0x11a}}, "BenchmarkOneEach": []*parse.Bench{{Name: "BenchmarkOneEach", N: 0x11a}},
"BenchmarkNoneToOne": []*Bench{{Name: "BenchmarkNoneToOne"}}, "BenchmarkNoneToOne": []*parse.Bench{{Name: "BenchmarkNoneToOne"}},
"BenchmarkTwoToOne": []*Bench{{Name: "BenchmarkTwoToOne"}}, "BenchmarkTwoToOne": []*parse.Bench{{Name: "BenchmarkTwoToOne"}},
"BenchmarkOneToTwo": []*Bench{ "BenchmarkOneToTwo": []*parse.Bench{
{Name: "BenchmarkOneToTwo"}, {Name: "BenchmarkOneToTwo"},
{Name: "BenchmarkOneToTwo"}, {Name: "BenchmarkOneToTwo"},
}, },
"BenchmarkTwoEach": []*Bench{ "BenchmarkTwoEach": []*parse.Bench{
{Name: "BenchmarkTwoEach", N: 0x12a}, {Name: "BenchmarkTwoEach", N: 0x12a},
{Name: "BenchmarkTwoEach", N: 0x22a}, {Name: "BenchmarkTwoEach", N: 0x22a},
}, },
@ -108,10 +110,10 @@ func TestCorrelate(t *testing.T) {
func TestBenchCmpSorting(t *testing.T) { func TestBenchCmpSorting(t *testing.T) {
c := []BenchCmp{ c := []BenchCmp{
{&Bench{Name: "BenchmarkMuchFaster", NsOp: 10, ord: 3}, &Bench{Name: "BenchmarkMuchFaster", NsOp: 1}}, {&parse.Bench{Name: "BenchmarkMuchFaster", NsOp: 10, Ord: 3}, &parse.Bench{Name: "BenchmarkMuchFaster", NsOp: 1}},
{&Bench{Name: "BenchmarkSameB", NsOp: 5, ord: 1}, &Bench{Name: "BenchmarkSameB", NsOp: 5}}, {&parse.Bench{Name: "BenchmarkSameB", NsOp: 5, Ord: 1}, &parse.Bench{Name: "BenchmarkSameB", NsOp: 5}},
{&Bench{Name: "BenchmarkSameA", NsOp: 5, ord: 2}, &Bench{Name: "BenchmarkSameA", NsOp: 5}}, {&parse.Bench{Name: "BenchmarkSameA", NsOp: 5, Ord: 2}, &parse.Bench{Name: "BenchmarkSameA", NsOp: 5}},
{&Bench{Name: "BenchmarkSlower", NsOp: 10, ord: 0}, &Bench{Name: "BenchmarkSlower", NsOp: 11}}, {&parse.Bench{Name: "BenchmarkSlower", NsOp: 10, Ord: 0}, &parse.Bench{Name: "BenchmarkSlower", NsOp: 11}},
} }
// Test just one magnitude-based sort order; they are symmetric. // Test just one magnitude-based sort order; they are symmetric.