--- /dev/null
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "sort"
+ "strconv"
+ "text/tabwriter"
+
+ "golang.org/x/tools/benchmark/parse"
+)
+
+var (
+ changedOnly = flag.Bool("changed", false, "show only benchmarks that have changed")
+ magSort = flag.Bool("mag", false, "sort benchmarks by magnitude of change")
+ best = flag.Bool("best", false, "compare best times from old and new")
+)
+
+const usageFooter = `
+Each input file should be from:
+ go test -run=NONE -bench=. > [old,new].txt
+
+Benchcmp compares old and new for each benchmark.
+
+If -test.benchmem=true is added to the "go test" command
+benchcmp will also compare memory allocations.
+`
+
+func main() {
+ fmt.Fprintf(os.Stderr, "benchcmp is deprecated in favor of benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat\n")
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, "usage: %s old.txt new.txt\n\n", os.Args[0])
+ flag.PrintDefaults()
+ fmt.Fprint(os.Stderr, usageFooter)
+ os.Exit(2)
+ }
+ flag.Parse()
+ if flag.NArg() != 2 {
+ flag.Usage()
+ }
+
+ before := parseFile(flag.Arg(0))
+ after := parseFile(flag.Arg(1))
+
+ cmps, warnings := Correlate(before, after)
+
+ for _, warn := range warnings {
+ fmt.Fprintln(os.Stderr, warn)
+ }
+
+ if len(cmps) == 0 {
+ fatal("benchcmp: no repeated benchmarks")
+ }
+
+ w := new(tabwriter.Writer)
+ w.Init(os.Stdout, 0, 0, 5, ' ', 0)
+ defer w.Flush()
+
+ var header bool // Has the header has been displayed yet for a given block?
+
+ if *magSort {
+ sort.Sort(ByDeltaNsPerOp(cmps))
+ } else {
+ sort.Sort(ByParseOrder(cmps))
+ }
+ for _, cmp := range cmps {
+ if !cmp.Measured(parse.NsPerOp) {
+ continue
+ }
+ if delta := cmp.DeltaNsPerOp(); !*changedOnly || delta.Changed() {
+ if !header {
+ fmt.Fprint(w, "benchmark\told ns/op\tnew ns/op\tdelta\n")
+ header = true
+ }
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cmp.Name(), formatNs(cmp.Before.NsPerOp), formatNs(cmp.After.NsPerOp), delta.Percent())
+ }
+ }
+
+ header = false
+ if *magSort {
+ sort.Sort(ByDeltaMBPerS(cmps))
+ }
+ for _, cmp := range cmps {
+ if !cmp.Measured(parse.MBPerS) {
+ continue
+ }
+ if delta := cmp.DeltaMBPerS(); !*changedOnly || delta.Changed() {
+ if !header {
+ fmt.Fprint(w, "\nbenchmark\told MB/s\tnew MB/s\tspeedup\n")
+ header = true
+ }
+ fmt.Fprintf(w, "%s\t%.2f\t%.2f\t%s\n", cmp.Name(), cmp.Before.MBPerS, cmp.After.MBPerS, delta.Multiple())
+ }
+ }
+
+ header = false
+ if *magSort {
+ sort.Sort(ByDeltaAllocsPerOp(cmps))
+ }
+ for _, cmp := range cmps {
+ if !cmp.Measured(parse.AllocsPerOp) {
+ continue
+ }
+ if delta := cmp.DeltaAllocsPerOp(); !*changedOnly || delta.Changed() {
+ if !header {
+ fmt.Fprint(w, "\nbenchmark\told allocs\tnew allocs\tdelta\n")
+ header = true
+ }
+ fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocsPerOp, cmp.After.AllocsPerOp, delta.Percent())
+ }
+ }
+
+ header = false
+ if *magSort {
+ sort.Sort(ByDeltaAllocedBytesPerOp(cmps))
+ }
+ for _, cmp := range cmps {
+ if !cmp.Measured(parse.AllocedBytesPerOp) {
+ continue
+ }
+ if delta := cmp.DeltaAllocedBytesPerOp(); !*changedOnly || delta.Changed() {
+ if !header {
+ fmt.Fprint(w, "\nbenchmark\told bytes\tnew bytes\tdelta\n")
+ header = true
+ }
+ fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocedBytesPerOp, cmp.After.AllocedBytesPerOp, cmp.DeltaAllocedBytesPerOp().Percent())
+ }
+ }
+}
+
+func fatal(msg interface{}) {
+ fmt.Fprintln(os.Stderr, msg)
+ os.Exit(1)
+}
+
+func parseFile(path string) parse.Set {
+ f, err := os.Open(path)
+ if err != nil {
+ fatal(err)
+ }
+ defer f.Close()
+ bb, err := parse.ParseSet(f)
+ if err != nil {
+ fatal(err)
+ }
+ if *best {
+ selectBest(bb)
+ }
+ return bb
+}
+
+func selectBest(bs parse.Set) {
+ for name, bb := range bs {
+ if len(bb) < 2 {
+ continue
+ }
+ ord := bb[0].Ord
+ best := bb[0]
+ for _, b := range bb {
+ if b.NsPerOp < best.NsPerOp {
+ b.Ord = ord
+ best = b
+ }
+ }
+ bs[name] = []*parse.Benchmark{best}
+ }
+}
+
+// formatNs formats ns measurements to expose a useful amount of
+// precision. It mirrors the ns precision logic of testing.B.
+func formatNs(ns float64) string {
+ prec := 0
+ switch {
+ case ns < 10:
+ prec = 2
+ case ns < 100:
+ prec = 1
+ }
+ return strconv.FormatFloat(ns, 'f', prec, 64)
+}