英文:
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 < 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 (
"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
答案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 (
"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) })))
}
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$
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论