改进Go语言的测试基准?

3
在学习Go语言时,我发现你可以使用fmt.Println(testing.Benchmark(BenchmarkFunction))来对函数进行基准测试,前提是必须设置以下内容:
func BenchmarkFunction(b *testing.B) {
    n := 42
    for i := 0; i < b.N; i++ {
        _ = Function(n)
    }
}

然而,由于每个方法的每个基准测试都会重复使用BenchmarkFunction代码(因此在DRY方面存在代码气味),是否有一种方式可以使用闭包(或其他方式)重写它,以便函数的基准测试可以重写为类似以下内容:
fmt.println(test.BenchmarkMyFunction(MyFunction(parameters...)))

我应该将它添加到我的代码中还是testing库中?


看一下标准库的一些测试文件。http://golang.org/src/pkg/fmt/fmt_test.go 似乎使用结构体来实现你想要的功能,其中一个是空接口 val(见第115行)。 - Intermernet
3个回答

4

实际上,这不是在Go中进行基准测试的正确方法。

实际标准是将基准测试代码放入具有名称BenchmarkXXX的函数中,其中XXX是任何您喜欢的内容。然后在定义这些文件的包上运行go test -bench=.go test为您运行所有基准测试。

如果您有具有略有不同参数的类似基准测试,则可以编写一个通用基准测试函数,所有其他基准测试仅调用该函数:

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

接着,您需要为每个特定的基准编写样板函数:

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) }

你可以在我写的这个包中找到这种模式的例子。
当然,虽然这不是非常好看,但我担心没有更简单的解决方案。对于找到代表你想要做的小型基准测试的清洁编码是有帮助的。

我对那些给我点踩的人完全没有问题。只是当人们不留下任何解释时,很难对此采取任何行动。 - fuz

4
这是一个真实、简单且符合DRY原则的Go基准测试,使用闭包。我想知道各种Substr函数在子字符串的大小(hi - lo)变化时的性能差异。
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()
}

输出:

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

1
如何将这个适应于*_test.go文件中的使用?我移除了package mainfunc main(),并在BenchmarkSubstrSize()中添加了b *Testing.B,但是我得到了fatal error: all goroutines are asleep - deadlock! - dmikalova

1

我不确定这是否可以被视为一种收益:

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\n", testing.Benchmark(bench(func() { f1(42) })))
        fmt.Printf("%v\n", testing.Benchmark(bench(func() { f2(24) })))
}

输出:

(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$ 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接