@@ -8,6 +8,9 @@ package testing
88
99import (
1010 "fmt"
11+ "io"
12+ "math"
13+ "strings"
1114 "time"
1215)
1316
@@ -41,14 +44,17 @@ type InternalBenchmark struct {
4144// affecting benchmark results.
4245type B struct {
4346 common
44- hasSub bool // TODO: should be in common, and atomic
45- start time.Time // TODO: should be in common
46- duration time.Duration // TODO: should be in common
47- N int
48- benchFunc func (b * B )
49- benchTime benchTimeFlag
50- timerOn bool
51- result BenchmarkResult
47+ hasSub bool // TODO: should be in common, and atomic
48+ start time.Time // TODO: should be in common
49+ duration time.Duration // TODO: should be in common
50+ context * benchContext
51+ N int
52+ benchFunc func (b * B )
53+ bytes int64
54+ missingBytes bool // one of the subbenchmarks does not have bytes set.
55+ benchTime benchTimeFlag
56+ timerOn bool
57+ result BenchmarkResult
5258}
5359
5460// StartTimer starts timing a test. This function is called automatically
@@ -82,15 +88,13 @@ func (b *B) ResetTimer() {
8288
8389// SetBytes records the number of bytes processed in a single operation.
8490// If this is called, the benchmark will report ns/op and MB/s.
85- func (b * B ) SetBytes (n int64 ) {
86- panic ("testing: unimplemented: B.SetBytes" )
87- }
91+ func (b * B ) SetBytes (n int64 ) { b .bytes = n }
8892
8993// ReportAllocs enables malloc statistics for this benchmark.
9094// It is equivalent to setting -test.benchmem, but it only affects the
9195// benchmark function that calls ReportAllocs.
9296func (b * B ) ReportAllocs () {
93- panic ( "testing: unimplemented: B.ReportAllocs" )
97+ return // TODO: implement
9498}
9599
96100// runN runs a single benchmark for the specified number of iterations.
@@ -119,13 +123,31 @@ func max(x, y int64) int64 {
119123// run1 runs the first iteration of benchFunc. It reports whether more
120124// iterations of this benchmarks should be run.
121125func (b * B ) run1 () bool {
126+ if ctx := b .context ; ctx != nil {
127+ // Extend maxLen, if needed.
128+ if n := len (b .name ); n > ctx .maxLen {
129+ ctx .maxLen = n + 8 // Add additional slack to avoid too many jumps in size.
130+ }
131+ }
122132 b .runN (1 )
123133 return ! b .hasSub
124134}
125135
126136// run executes the benchmark.
127137func (b * B ) run () {
138+ if b .context != nil {
139+ // Running go test --test.bench
140+ b .processBench (b .context ) // calls doBench and prints results
141+ } else {
142+ // Running func Benchmark.
143+ b .doBench ()
144+ }
145+ }
146+
147+ func (b * B ) doBench () BenchmarkResult {
148+ // in upstream, this uses a goroutine
128149 b .launch ()
150+ return b .result
129151}
130152
131153// launch launches the benchmark function. It gradually increases the number
@@ -159,13 +181,14 @@ func (b *B) launch() {
159181 n = min (n , 1e9 )
160182 b .runN (int (n ))
161183 }
162- b .result = BenchmarkResult {b .N , b .duration }
184+ b .result = BenchmarkResult {b .N , b .duration , b . bytes }
163185}
164186
165187// BenchmarkResult contains the results of a benchmark run.
166188type BenchmarkResult struct {
167- N int // The number of iterations.
168- T time.Duration // The total time taken.
189+ N int // The number of iterations.
190+ T time.Duration // The total time taken.
191+ Bytes int64 // Bytes processed in one iteration.
169192}
170193
171194// NsPerOp returns the "ns/op" metric.
@@ -176,6 +199,14 @@ func (r BenchmarkResult) NsPerOp() int64 {
176199 return r .T .Nanoseconds () / int64 (r .N )
177200}
178201
202+ // mbPerSec returns the "MB/s" metric.
203+ func (r BenchmarkResult ) mbPerSec () float64 {
204+ if r .Bytes <= 0 || r .T <= 0 || r .N <= 0 {
205+ return 0
206+ }
207+ return (float64 (r .Bytes ) * float64 (r .N ) / 1e6 ) / r .T .Seconds ()
208+ }
209+
179210// AllocsPerOp returns the "allocs/op" metric,
180211// which is calculated as r.MemAllocs / r.N.
181212func (r BenchmarkResult ) AllocsPerOp () int64 {
@@ -188,41 +219,127 @@ func (r BenchmarkResult) AllocedBytesPerOp() int64 {
188219 return 0 // Dummy version to allow running e.g. golang.org/test/fibo.go
189220}
190221
222+ // String returns a summary of the benchmark results.
223+ // It follows the benchmark result line format from
224+ // https://golang.org/design/14313-benchmark-format, not including the
225+ // benchmark name.
226+ // Extra metrics override built-in metrics of the same name.
227+ // String does not include allocs/op or B/op, since those are reported
228+ // by MemString.
229+ func (r BenchmarkResult ) String () string {
230+ buf := new (strings.Builder )
231+ fmt .Fprintf (buf , "%8d" , r .N )
232+
233+ // Get ns/op as a float.
234+ ns := float64 (r .T .Nanoseconds ()) / float64 (r .N )
235+ if ns != 0 {
236+ buf .WriteByte ('\t' )
237+ prettyPrint (buf , ns , "ns/op" )
238+ }
239+
240+ if mbs := r .mbPerSec (); mbs != 0 {
241+ fmt .Fprintf (buf , "\t %7.2f MB/s" , mbs )
242+ }
243+ return buf .String ()
244+ }
245+
246+ func prettyPrint (w io.Writer , x float64 , unit string ) {
247+ // Print all numbers with 10 places before the decimal point
248+ // and small numbers with four sig figs. Field widths are
249+ // chosen to fit the whole part in 10 places while aligning
250+ // the decimal point of all fractional formats.
251+ var format string
252+ switch y := math .Abs (x ); {
253+ case y == 0 || y >= 999.95 :
254+ format = "%10.0f %s"
255+ case y >= 99.995 :
256+ format = "%12.1f %s"
257+ case y >= 9.9995 :
258+ format = "%13.2f %s"
259+ case y >= 0.99995 :
260+ format = "%14.3f %s"
261+ case y >= 0.099995 :
262+ format = "%15.4f %s"
263+ case y >= 0.0099995 :
264+ format = "%16.5f %s"
265+ case y >= 0.00099995 :
266+ format = "%17.6f %s"
267+ default :
268+ format = "%18.7f %s"
269+ }
270+ fmt .Fprintf (w , format , x , unit )
271+ }
272+
273+ type benchContext struct {
274+ maxLen int // The largest recorded benchmark name.
275+ }
276+
191277func runBenchmarks (benchmarks []InternalBenchmark ) bool {
192278 if len (benchmarks ) == 0 {
193279 return true
194280 }
281+ ctx := & benchContext {}
282+ for _ , Benchmark := range benchmarks {
283+ if l := len (Benchmark .Name ); l > ctx .maxLen {
284+ ctx .maxLen = l
285+ }
286+ }
195287 main := & B {
196288 common : common {
197289 name : "Main" ,
198290 },
199291 benchTime : benchTime ,
200292 benchFunc : func (b * B ) {
201293 for _ , Benchmark := range benchmarks {
202- if flagVerbose {
203- fmt .Printf ("=== RUN %s\n " , Benchmark .Name )
204- }
205294 b .Run (Benchmark .Name , Benchmark .F )
206- fmt .Printf ("--- Result: %d ns/op\n " , b .result .NsPerOp ())
207295 }
208296 },
297+ context : ctx ,
209298 }
210299
211300 main .runN (1 )
212301 return true
213302}
214303
304+ // processBench runs bench b and prints the results.
305+ func (b * B ) processBench (ctx * benchContext ) {
306+ benchName := b .name
307+
308+ if ctx != nil {
309+ fmt .Printf ("%-*s\t " , ctx .maxLen , benchName )
310+ }
311+ r := b .doBench ()
312+ if b .failed {
313+ // The output could be very long here, but probably isn't.
314+ // We print it all, regardless, because we don't want to trim the reason
315+ // the benchmark failed.
316+ fmt .Printf ("--- FAIL: %s\n %s" , benchName , "" ) // b.output)
317+ return
318+ }
319+ if ctx != nil {
320+ results := r .String ()
321+ fmt .Println (results )
322+ }
323+ }
324+
215325// Run benchmarks f as a subbenchmark with the given name. It reports
216326// true if the subbenchmark succeeded.
217327//
218328// A subbenchmark is like any other benchmark. A benchmark that calls Run at
219329// least once will not be measured itself and will be called once with N=1.
220330func (b * B ) Run (name string , f func (b * B )) bool {
331+ if b .level > 0 {
332+ name = b .name + "/" + name
333+ }
221334 b .hasSub = true
222335 sub := & B {
223- common : common {name : name },
336+ common : common {
337+ name : name ,
338+ level : b .level + 1 ,
339+ },
224340 benchFunc : f ,
225341 benchTime : b .benchTime ,
342+ context : b .context ,
226343 }
227344 if sub .run1 () {
228345 sub .run ()
@@ -240,6 +357,15 @@ func (b *B) add(other BenchmarkResult) {
240357 // in sequence in a single benchmark.
241358 r .N = 1
242359 r .T += time .Duration (other .NsPerOp ())
360+ if other .Bytes == 0 {
361+ // Summing Bytes is meaningless in aggregate if not all subbenchmarks
362+ // set it.
363+ b .missingBytes = true
364+ r .Bytes = 0
365+ }
366+ if ! b .missingBytes {
367+ r .Bytes += other .Bytes
368+ }
243369}
244370
245371// A PB is used by RunParallel for running parallel benchmarks.
0 commit comments