You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
4.1 KiB

  1. // Copyright 2014 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. package main
  5. import (
  6. "flag"
  7. "fmt"
  8. "os"
  9. "sort"
  10. "strconv"
  11. "text/tabwriter"
  12. "golang.org/x/tools/benchmark/parse"
  13. )
  14. var (
  15. changedOnly = flag.Bool("changed", false, "show only benchmarks that have changed")
  16. magSort = flag.Bool("mag", false, "sort benchmarks by magnitude of change")
  17. best = flag.Bool("best", false, "compare best times from old and new")
  18. )
  19. const usageFooter = `
  20. Each input file should be from:
  21. go test -run=NONE -bench=. > [old,new].txt
  22. Benchcmp compares old and new for each benchmark.
  23. If -test.benchmem=true is added to the "go test" command
  24. benchcmp will also compare memory allocations.
  25. `
  26. func main() {
  27. flag.Usage = func() {
  28. fmt.Fprintf(os.Stderr, "usage: %s old.txt new.txt\n\n", os.Args[0])
  29. flag.PrintDefaults()
  30. fmt.Fprint(os.Stderr, usageFooter)
  31. os.Exit(2)
  32. }
  33. flag.Parse()
  34. if flag.NArg() != 2 {
  35. flag.Usage()
  36. }
  37. before := parseFile(flag.Arg(0))
  38. after := parseFile(flag.Arg(1))
  39. cmps, warnings := Correlate(before, after)
  40. for _, warn := range warnings {
  41. fmt.Fprintln(os.Stderr, warn)
  42. }
  43. if len(cmps) == 0 {
  44. fatal("benchcmp: no repeated benchmarks")
  45. }
  46. w := new(tabwriter.Writer)
  47. w.Init(os.Stdout, 0, 0, 5, ' ', 0)
  48. defer w.Flush()
  49. var header bool // Has the header has been displayed yet for a given block?
  50. if *magSort {
  51. sort.Sort(ByDeltaNsPerOp(cmps))
  52. } else {
  53. sort.Sort(ByParseOrder(cmps))
  54. }
  55. for _, cmp := range cmps {
  56. if !cmp.Measured(parse.NsPerOp) {
  57. continue
  58. }
  59. if delta := cmp.DeltaNsPerOp(); !*changedOnly || delta.Changed() {
  60. if !header {
  61. fmt.Fprint(w, "benchmark\told ns/op\tnew ns/op\tdelta\n")
  62. header = true
  63. }
  64. fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cmp.Name(), formatNs(cmp.Before.NsPerOp), formatNs(cmp.After.NsPerOp), delta.Percent())
  65. }
  66. }
  67. header = false
  68. if *magSort {
  69. sort.Sort(ByDeltaMBPerS(cmps))
  70. }
  71. for _, cmp := range cmps {
  72. if !cmp.Measured(parse.MBPerS) {
  73. continue
  74. }
  75. if delta := cmp.DeltaMBPerS(); !*changedOnly || delta.Changed() {
  76. if !header {
  77. fmt.Fprint(w, "\nbenchmark\told MB/s\tnew MB/s\tspeedup\n")
  78. header = true
  79. }
  80. fmt.Fprintf(w, "%s\t%.2f\t%.2f\t%s\n", cmp.Name(), cmp.Before.MBPerS, cmp.After.MBPerS, delta.Multiple())
  81. }
  82. }
  83. header = false
  84. if *magSort {
  85. sort.Sort(ByDeltaAllocsPerOp(cmps))
  86. }
  87. for _, cmp := range cmps {
  88. if !cmp.Measured(parse.AllocsPerOp) {
  89. continue
  90. }
  91. if delta := cmp.DeltaAllocsPerOp(); !*changedOnly || delta.Changed() {
  92. if !header {
  93. fmt.Fprint(w, "\nbenchmark\told allocs\tnew allocs\tdelta\n")
  94. header = true
  95. }
  96. fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocsPerOp, cmp.After.AllocsPerOp, delta.Percent())
  97. }
  98. }
  99. header = false
  100. if *magSort {
  101. sort.Sort(ByDeltaAllocedBytesPerOp(cmps))
  102. }
  103. for _, cmp := range cmps {
  104. if !cmp.Measured(parse.AllocedBytesPerOp) {
  105. continue
  106. }
  107. if delta := cmp.DeltaAllocedBytesPerOp(); !*changedOnly || delta.Changed() {
  108. if !header {
  109. fmt.Fprint(w, "\nbenchmark\told bytes\tnew bytes\tdelta\n")
  110. header = true
  111. }
  112. fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocedBytesPerOp, cmp.After.AllocedBytesPerOp, cmp.DeltaAllocedBytesPerOp().Percent())
  113. }
  114. }
  115. }
  116. func fatal(msg interface{}) {
  117. fmt.Fprintln(os.Stderr, msg)
  118. os.Exit(1)
  119. }
  120. func parseFile(path string) parse.Set {
  121. f, err := os.Open(path)
  122. if err != nil {
  123. fatal(err)
  124. }
  125. defer f.Close()
  126. bb, err := parse.ParseSet(f)
  127. if err != nil {
  128. fatal(err)
  129. }
  130. if *best {
  131. selectBest(bb)
  132. }
  133. return bb
  134. }
  135. func selectBest(bs parse.Set) {
  136. for name, bb := range bs {
  137. if len(bb) < 2 {
  138. continue
  139. }
  140. ord := bb[0].Ord
  141. best := bb[0]
  142. for _, b := range bb {
  143. if b.NsPerOp < best.NsPerOp {
  144. b.Ord = ord
  145. best = b
  146. }
  147. }
  148. bs[name] = []*parse.Benchmark{best}
  149. }
  150. }
  151. // formatNs formats ns measurements to expose a useful amount of
  152. // precision. It mirrors the ns precision logic of testing.B.
  153. func formatNs(ns float64) string {
  154. prec := 0
  155. switch {
  156. case ns < 10:
  157. prec = 2
  158. case ns < 100:
  159. prec = 1
  160. }
  161. return strconv.FormatFloat(ns, 'f', prec, 64)
  162. }