Go Home Page
The Go Programming Language

Source file src/pkg/testing/benchmark.go

// Copyright 2009 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 testing

import (
    "flag"
    "fmt"
    "os"
    "time"
)

var matchBenchmarks = flag.String("benchmarks", "", "regular expression to select benchmarks to run")

// An internal type but exported because it is cross-package; part of the implementation
// of gotest.
type Benchmark struct {
    Name string
    F    func(b *B)
}

// B is a type passed to Benchmark functions to manage benchmark
// timing and to specify the number of iterations to run.
type B struct {
    N         int
    benchmark Benchmark
    ns        int64
    bytes     int64
    start     int64
}

// StartTimer starts timing a test.  This function is called automatically
// before a benchmark starts, but it can also used to resume timing after
// a call to StopTimer.
func (b *B) StartTimer() { b.start = time.Nanoseconds() }

// StopTimer stops timing a test.  This can be used to pause the timer
// while performing complex initialization that you don't
// want to measure.
func (b *B) StopTimer() {
    if b.start > 0 {
        b.ns += time.Nanoseconds() - b.start
    }
    b.start = 0
}

// ResetTimer stops the timer and sets the elapsed benchmark time to zero.
func (b *B) ResetTimer() {
    b.start = 0
    b.ns = 0
}

// SetBytes records the number of bytes processed in a single operation.
// If this is called, the benchmark will report ns/op and MB/s.
func (b *B) SetBytes(n int64) { b.bytes = n }

func (b *B) nsPerOp() int64 {
    if b.N <= 0 {
        return 0
    }
    return b.ns / int64(b.N)
}

// runN runs a single benchmark for the specified number of iterations.
func (b *B) runN(n int) {
    b.N = n
    b.ResetTimer()
    b.StartTimer()
    b.benchmark.F(b)
    b.StopTimer()
}

func min(x, y int) int {
    if x > y {
        return y
    }
    return x
}

func max(x, y int) int {
    if x < y {
        return y
    }
    return x
}

// roundDown10 rounds a number down to the nearest power of 10.
func roundDown10(n int) int {
    var tens = 0
    // tens = floor(log_10(n))
    for n > 10 {
        n = n / 10
        tens++
    }
    // result = 10^tens
    result := 1
    for i := 0; i < tens; i++ {
        result *= 10
    }
    return result
}

// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
func roundUp(n int) int {
    base := roundDown10(n)
    if n < (2 * base) {
        return 2 * base
    }
    if n < (5 * base) {
        return 5 * base
    }
    return 10 * base
}

// run times the benchmark function.  It gradually increases the number
// of benchmark iterations until the benchmark runs for a second in order
// to get a reasonable measurement.  It prints timing information in this form
//		testing.BenchmarkHello	100000		19 ns/op
func (b *B) run() {
    // Run the benchmark for a single iteration in case it's expensive.
    n := 1
    b.runN(n)
    // Run the benchmark for at least a second.
    for b.ns < 1e9 && n < 1e9 {
        last := n
        // Predict iterations/sec.
        if b.nsPerOp() == 0 {
            n = 1e9
        } else {
            n = 1e9 / int(b.nsPerOp())
        }
        // Run more iterations than we think we'll need for a second (1.5x).
        // Don't grow too fast in case we had timing errors previously.
        // Be sure to run at least one more than last time.
        n = max(min(n+n/2, 100*last), last+1)
        // Round up to something easy to read.
        n = roundUp(n)
        b.runN(n)
    }
    ns := b.nsPerOp()
    mb := ""
    if ns > 0 && b.bytes > 0 {
        mb = fmt.Sprintf("\t%7.2f MB/s", (float64(b.bytes)/1e6)/(float64(ns)/1e9))
    }
    fmt.Printf("%s\t%8d\t%10d ns/op%s\n", b.benchmark.Name, b.N, b.nsPerOp(), mb)
}

// An internal function but exported because it is cross-package; part of the implementation
// of gotest.
func RunBenchmarks(benchmarks []Benchmark) {
    // If no flag was specified, don't run benchmarks.
    if len(*matchBenchmarks) == 0 {
        return
    }
    re, err := CompileRegexp(*matchBenchmarks)
    if err != "" {
        println("invalid regexp for -benchmarks:", err)
        os.Exit(1)
    }
    for _, Benchmark := range benchmarks {
        if !re.MatchString(Benchmark.Name) {
            continue
        }
        b := &B{benchmark: Benchmark}
        b.run()
    }
}