改进测试。基准测试?

In learning about Go, I see that you can benchmark a function using fmt.Println(testing.Benchmark(BenchmarkFunction)), having set up the following:

func BenchmarkFunction(b *testing.B) {
    n := 42
    for i := 0; i < b.N; i++ {
        _ = Function(n)
    }
}

However, as the BenchmarkFunction code would be repeated for every benchmark of every method you would do this for (hence a code smell, in terms of DRY), is there a way that this could be rewritten using closures (or in some other manner) such that a benchmark of a function could be rewritten something like:

fmt.println(test.BenchmarkMyFunction(MyFunction(parameters...)))

and added either into my code or the testing library?

Actually, this is not the right way to do benchmarks in Go.

The actual standard is to put your benchmarking code into functions that have names BenchmarkXXX where XXX is whatever you like. Then you run go test -bench=. on the package that defines these files. go test is running all the benchmarks for you.

If you have similar benchmarks with slightly different parameters, you can write a generic benchmarking function that is just called by all the other benchmarks:

func genericBenchmarkFoo(b *testing.B, param int) { ... }

And then you write a boilerplate function for each specific benchmark:

func BenchmarkFoo1(b *testing.B) { genericBenchmarkFoo(b, 1) }
func BenchmarkFoo2(b *testing.B) { genericBenchmarkFoo(b, 2) }
func BenchmarkFoo3(b *testing.B) { genericBenchmarkFoo(b, 3) }
func BenchmarkFoo4(b *testing.B) { genericBenchmarkFoo(b, 4) }

You can find an example of this pattern in this package I wrote.

Of course this is not exactly beautiful but I am afraid there is not an easier solution. It's clean coding however to find a small set of benchmarks that represent what you want to do.

I'm not sure if this can be considered a gain:

package main

import (
        "fmt"
        "testing"
)

func f1(n int) (s int) {
        for i := 0; i < n; i++ {
                s += i
        }
        return
}

func f2(n int) (s int) {
        for i := 0; i < n; i++ {
                s += 2 * i
        }
        return
}

func bench(f func()) func(b *testing.B) {
        return func(b *testing.B) {
                for i := 0; i < b.N; i++ {
                        f()
                }
        }
}

func main() {
        fmt.Printf("%v
", testing.Benchmark(bench(func() { f1(42) })))
        fmt.Printf("%v
", testing.Benchmark(bench(func() { f2(24) })))
}

Output:

(16:55) jnml@fsc-r550:~/src/tmp/SO/16920669$ go run main.go 
50000000            68.4 ns/op
50000000            35.8 ns/op
(16:55) jnml@fsc-r550:~/src/tmp/SO/16920669$ 

Here's a real, simple, and DRY Go benchmark, which uses closures. I wanted to know how the performance of a various Substr functions varied with the size (hi - lo) of the substring.

package main

import (
    "fmt"
    "strings"
    "testing"
)

func Substr1(str string, lo, hi int) string {
    return string([]byte(str[lo:hi]))
}

func Substr2(str string, lo, hi int) string {
    sub := str[lo:hi]
    return (sub + " ")[:len(sub)]
}

func Substr3(str string, lo, hi int) string {
    sub := str[lo:hi]
    if len(sub) == 0 {
        return ""
    }
    return sub[0:1] + sub[1:]
}

var substrFunctions = []struct {
    name     string
    function func(str string, lo, hi int) string
}{
    {"Substr1", Substr1},
    {"Substr2", Substr2},
    {"Substr3", Substr3},
}

var substrBenchmarks = []struct {
    name                 string
    strLen, subLo, subHi int
}{
    {"Zero  ", 1, 1, 1},
    {"Small ", 4, 1, 4},
    {"Medium", 256, 1, 256},
    {"Large ", 4096, 1, 4096},
}

func BenchmarkSubstrSize() {
    fmt.Println("BenchmarkSubstrSize:")
    for _, benchmark := range substrBenchmarks {
        str := strings.Repeat("abc", benchmark.strLen)
        for _, function := range substrFunctions {
            benchmarkFunc := func(b *testing.B) {
                b.ResetTimer()
                for i := 0; i < b.N; i++ {
                    function.function(str, benchmark.subLo, benchmark.subHi)
                }
                b.StopTimer()
            }
            results := testing.Benchmark(benchmarkFunc)
            fmt.Println(benchmark.name, function.name, results)
        }
    }
}

func main() {
    BenchmarkSubstrSize()
}

Output:

BenchmarkSubstrSize:
Zero    Substr1   50000000    54.8 ns/op
Zero    Substr2  100000000    19.6 ns/op
Zero    Substr3  500000000     6.66 ns/op
Small   Substr1   20000000    95.7 ns/op
Small   Substr2   50000000    70.4 ns/op
Small   Substr3   50000000    70.1 ns/op
Medium  Substr1    5000000   380 ns/op
Medium  Substr2   10000000   229 ns/op
Medium  Substr3   10000000   213 ns/op
Large   Substr1     500000  4290 ns/op
Large   Substr2    1000000  2007 ns/op
Large   Substr3    1000000  2275 ns/op