Source file src/pkg/testing/benchmark.go
1
2
3
4
5 package testing
6
7 import (
8 "flag"
9 "fmt"
10 "os"
11 "runtime"
12 "sync"
13 "time"
14 )
15
16 var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run")
17 var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark")
18 var benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks")
19
20
21 var benchmarkLock sync.Mutex
22
23
24 var memStats runtime.MemStats
25
26
27
28 type InternalBenchmark struct {
29 Name string
30 F func(b *B)
31 }
32
33
34
35 type B struct {
36 common
37 N int
38 benchmark InternalBenchmark
39 bytes int64
40 timerOn bool
41 showAllocResult bool
42 result BenchmarkResult
43
44 startAllocs uint64
45 startBytes uint64
46
47 netAllocs uint64
48 netBytes uint64
49 }
50
51
52
53
54 func (b *B) StartTimer() {
55 if !b.timerOn {
56 runtime.ReadMemStats(&memStats)
57 b.startAllocs = memStats.Mallocs
58 b.startBytes = memStats.TotalAlloc
59 b.start = time.Now()
60 b.timerOn = true
61 }
62 }
63
64
65
66
67 func (b *B) StopTimer() {
68 if b.timerOn {
69 b.duration += time.Now().Sub(b.start)
70 runtime.ReadMemStats(&memStats)
71 b.netAllocs += memStats.Mallocs - b.startAllocs
72 b.netBytes += memStats.TotalAlloc - b.startBytes
73 b.timerOn = false
74 }
75 }
76
77
78
79 func (b *B) ResetTimer() {
80 if b.timerOn {
81 runtime.ReadMemStats(&memStats)
82 b.startAllocs = memStats.Mallocs
83 b.startBytes = memStats.TotalAlloc
84 b.start = time.Now()
85 }
86 b.duration = 0
87 b.netAllocs = 0
88 b.netBytes = 0
89 }
90
91
92
93 func (b *B) SetBytes(n int64) { b.bytes = n }
94
95
96
97
98 func (b *B) ReportAllocs() {
99 b.showAllocResult = true
100 }
101
102 func (b *B) nsPerOp() int64 {
103 if b.N <= 0 {
104 return 0
105 }
106 return b.duration.Nanoseconds() / int64(b.N)
107 }
108
109
110 func (b *B) runN(n int) {
111 benchmarkLock.Lock()
112 defer benchmarkLock.Unlock()
113
114
115 runtime.GC()
116 b.N = n
117 b.ResetTimer()
118 b.StartTimer()
119 b.benchmark.F(b)
120 b.StopTimer()
121 }
122
123 func min(x, y int) int {
124 if x > y {
125 return y
126 }
127 return x
128 }
129
130 func max(x, y int) int {
131 if x < y {
132 return y
133 }
134 return x
135 }
136
137
138 func roundDown10(n int) int {
139 var tens = 0
140
141 for n > 10 {
142 n = n / 10
143 tens++
144 }
145
146 result := 1
147 for i := 0; i < tens; i++ {
148 result *= 10
149 }
150 return result
151 }
152
153
154 func roundUp(n int) int {
155 base := roundDown10(n)
156 if n < (2 * base) {
157 return 2 * base
158 }
159 if n < (5 * base) {
160 return 5 * base
161 }
162 return 10 * base
163 }
164
165
166 func (b *B) run() BenchmarkResult {
167 go b.launch()
168 <-b.signal
169 return b.result
170 }
171
172
173
174
175
176
177 func (b *B) launch() {
178
179 n := 1
180
181
182
183 defer func() {
184 b.signal <- b
185 }()
186
187 b.runN(n)
188
189 d := *benchTime
190 for !b.failed && b.duration < d && n < 1e9 {
191 last := n
192
193 if b.nsPerOp() == 0 {
194 n = 1e9
195 } else {
196 n = int(d.Nanoseconds() / b.nsPerOp())
197 }
198
199
200
201 n = max(min(n+n/2, 100*last), last+1)
202
203 n = roundUp(n)
204 b.runN(n)
205 }
206 b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes}
207 }
208
209
210 type BenchmarkResult struct {
211 N int
212 T time.Duration
213 Bytes int64
214 MemAllocs uint64
215 MemBytes uint64
216 }
217
218 func (r BenchmarkResult) NsPerOp() int64 {
219 if r.N <= 0 {
220 return 0
221 }
222 return r.T.Nanoseconds() / int64(r.N)
223 }
224
225 func (r BenchmarkResult) mbPerSec() float64 {
226 if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 {
227 return 0
228 }
229 return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
230 }
231
232 func (r BenchmarkResult) AllocsPerOp() int64 {
233 if r.N <= 0 {
234 return 0
235 }
236 return int64(r.MemAllocs) / int64(r.N)
237 }
238
239 func (r BenchmarkResult) AllocedBytesPerOp() int64 {
240 if r.N <= 0 {
241 return 0
242 }
243 return int64(r.MemBytes) / int64(r.N)
244 }
245
246 func (r BenchmarkResult) String() string {
247 mbs := r.mbPerSec()
248 mb := ""
249 if mbs != 0 {
250 mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
251 }
252 nsop := r.NsPerOp()
253 ns := fmt.Sprintf("%10d ns/op", nsop)
254 if r.N > 0 && nsop < 100 {
255
256
257 if nsop < 10 {
258 ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
259 } else {
260 ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
261 }
262 }
263 return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb)
264 }
265
266 func (r BenchmarkResult) MemString() string {
267 return fmt.Sprintf("%8d B/op\t%8d allocs/op",
268 r.AllocedBytesPerOp(), r.AllocsPerOp())
269 }
270
271
272
273 func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) {
274
275 if len(*matchBenchmarks) == 0 {
276 return
277 }
278 for _, Benchmark := range benchmarks {
279 matched, err := matchString(*matchBenchmarks, Benchmark.Name)
280 if err != nil {
281 fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err)
282 os.Exit(1)
283 }
284 if !matched {
285 continue
286 }
287 for _, procs := range cpuList {
288 runtime.GOMAXPROCS(procs)
289 b := &B{
290 common: common{
291 signal: make(chan interface{}),
292 },
293 benchmark: Benchmark,
294 }
295 benchName := Benchmark.Name
296 if procs != 1 {
297 benchName = fmt.Sprintf("%s-%d", Benchmark.Name, procs)
298 }
299 fmt.Printf("%s\t", benchName)
300 r := b.run()
301 if b.failed {
302
303
304
305 fmt.Printf("--- FAIL: %s\n%s", benchName, b.output)
306 continue
307 }
308 results := r.String()
309 if *benchmarkMemory || b.showAllocResult {
310 results += "\t" + r.MemString()
311 }
312 fmt.Println(results)
313
314
315 if len(b.output) > 0 {
316 b.trimOutput()
317 fmt.Printf("--- BENCH: %s\n%s", benchName, b.output)
318 }
319 if p := runtime.GOMAXPROCS(-1); p != procs {
320 fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
321 }
322 }
323 }
324 }
325
326
327 func (b *B) trimOutput() {
328
329
330
331 const maxNewlines = 10
332 for nlCount, j := 0, 0; j < len(b.output); j++ {
333 if b.output[j] == '\n' {
334 nlCount++
335 if nlCount >= maxNewlines {
336 b.output = append(b.output[:j], "\n\t... [output truncated]\n"...)
337 break
338 }
339 }
340 }
341 }
342
343
344
345 func Benchmark(f func(b *B)) BenchmarkResult {
346 b := &B{
347 common: common{
348 signal: make(chan interface{}),
349 },
350 benchmark: InternalBenchmark{"", f},
351 }
352 return b.run()
353 }
View as plain text