改进Go的testing.Benchmark?

huangapple go评论72阅读模式
英文:

Improving testing.Benchmark for Go?

问题

在学习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库中?

英文:

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 &lt; 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?

答案1

得分: 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) }

你可以在我写的这个中找到这种模式的示例。

当然,这并不是非常美观,但恐怕没有更简单的解决方案。然而,找到一小组能代表你想要做的事情的基准测试是一种良好的编码习惯。

英文:

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.

答案2

得分: 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
英文:

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 (
	&quot;fmt&quot;
	&quot;strings&quot;
	&quot;testing&quot;
)

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 + &quot; &quot;)[:len(sub)]
}

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

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

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

func BenchmarkSubstrSize() {
	fmt.Println(&quot;BenchmarkSubstrSize:&quot;)
	for _, benchmark := range substrBenchmarks {
		str := strings.Repeat(&quot;abc&quot;, benchmark.strLen)
		for _, function := range substrFunctions {
			benchmarkFunc := func(b *testing.B) {
				b.ResetTimer()
				for i := 0; i &lt; 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

答案3

得分: 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$
英文:

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

package main

import (
        &quot;fmt&quot;
        &quot;testing&quot;
)

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

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

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

func main() {
        fmt.Printf(&quot;%v\n&quot;, testing.Benchmark(bench(func() { f1(42) })))
        fmt.Printf(&quot;%v\n&quot;, 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$ 

huangapple
  • 本文由 发表于 2013年6月4日 22:39:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/16920669.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定