是否可以动态 运行 基准测试?
Is it possible to dynamically run benchmark tests?
我有几个不同的接口实现,以及我想测试它们的各种因素。最终目标是为不同情况下的不同实施制作结果网格。
我可以为每种可能的组合编写测试,但这会让人筋疲力尽:
func Benchmark_ImplA_N100_X300(b *testing.B){
impl := newImplA(100,300)
runBenchmark(b,impl)
}
我添加的组合越多,我就越需要copy/paste。这很快就会变得很麻烦。
我很想做这样的事情:
tests := []testing.InternalBenchmark{}
for _, n := range []int{50,100,500,10000}{
for _,x := range []int{300,200}{
for name, gen := range implementations{
impl = gen(n,x)
bm := testing.InternalBenchmark{Name: fmt.Sprint(name,x,n)}
bm.F = func(b *testing.B){
runBench(b,impl)
}
tests = append(tests,bm)
}
}
}
testing.RunBenchmarks(anything, tests)
这样我就可以更新组合列表,基准测试会神奇地 运行 所有组合。我在 main 和 TestMain 中尝试过类似的东西,但没有任何输出。我不确定是我用错了,还是测试包只是在做一些有趣的事情。
我真的不在乎 go test
工具是否可以处理它或者是否有其他方法。
是的,这是可能的。在您的测试文件 (xxx_test.go
) 中创建您自己的 TestMain()
函数,并在组装动态基准用例(结构 testing.InternalBenchmark
), call testing.Main()
which properly parses command line flags, creates and sets up testing.M
, and prepares and calls testing.RunBenchmarks()
的值)后在其中创建函数。这样您的动态基准仍将是 运行由 go test
.
启用
注意:testing.Main()
永远不会 return 因为它调用 os.Exit()
. If you want to perform further logging and calculations on the benchmark results, you may also call testing.MainStart()
.Run()
(which is what testing.Main()
does), and you may pass the exit code which is returned by M.Run()
到 os.Exit()
。
下面是一个完整的测试文件,您可以简单地 运行 和 go test -bench .
。
输出是:动态生成测试的基准测试结果(具有不同参数的不同实现):
testing: warning: no tests to run
PASS
main.EngineA[impl=0, n=50, x=300]-4 100000 16716 ns/op
main.EngineB[impl=1, n=50, x=300]-4 100000 24788 ns/op
main.EngineA[impl=0, n=50, x=200]-4 100000 10764 ns/op
main.EngineB[impl=1, n=50, x=200]-4 100000 16415 ns/op
main.EngineA[impl=0, n=100, x=300]-4 50000 33426 ns/op
main.EngineB[impl=1, n=100, x=300]-4 30000 48466 ns/op
main.EngineA[impl=0, n=100, x=200]-4 50000 20452 ns/op
main.EngineB[impl=1, n=100, x=200]-4 50000 33134 ns/op
main.EngineA[impl=0, n=500, x=300]-4 10000 163087 ns/op
main.EngineB[impl=1, n=500, x=300]-4 5000 238043 ns/op
main.EngineA[impl=0, n=500, x=200]-4 10000 102662 ns/op
main.EngineB[impl=1, n=500, x=200]-4 10000 163113 ns/op
main.EngineA[impl=0, n=1000, x=300]-4 5000 319744 ns/op
main.EngineB[impl=1, n=1000, x=300]-4 3000 512077 ns/op
main.EngineA[impl=0, n=1000, x=200]-4 10000 201036 ns/op
main.EngineB[impl=1, n=1000, x=200]-4 5000 325714 ns/op
ok _/xxx/src/play 27.307s
和来源(测试文件,例如dynbench_test.go
):
package main
import (
"fmt"
"testing"
)
type Engine interface {
Calc()
}
type EngineA struct{ n, x int }
func (e EngineA) Calc() {
for i := 0; i < e.n; i++ {
a, b := make([]byte, e.x), make([]byte, e.x)
copy(b, a)
}
}
type EngineB struct{ n, x int }
func (e EngineB) Calc() {
for i := 0; i < e.n*2; i++ {
a, b := make([]byte, e.x/2), make([]byte, e.x/2)
copy(b, a)
}
}
func TestMain(m *testing.M) {
implementations := [](func(n, x int) Engine){
func(n, x int) Engine { return EngineA{n, x} },
func(n, x int) Engine { return EngineB{n, x} },
}
benchmarks := []testing.InternalBenchmark{}
for _, n := range []int{50, 100, 500, 1000} {
for _, x := range []int{300, 200} {
for name, gen := range implementations {
impl := gen(n, x)
bm := testing.InternalBenchmark{
Name: fmt.Sprintf("%T[impl=%d, n=%d, x=%d]", impl, name, n, x)}
bm.F = func(b *testing.B) {
for i := 0; i < b.N; i++ {
impl.Calc()
}
}
benchmarks = append(benchmarks, bm)
}
}
}
anything := func(pat, str string) (bool, error) { return true, nil }
testing.Main(anything, nil, benchmarks, nil)
}
注释 #2:
testing.Main()
、testing.MainStart()
和 testing.InternalBenchmark
可能会在 Go 的未来版本中更改(或删除):
An internal function / internal type but exported because it is cross-package; part of or called by the implementation of the "go test" command.
我想阅读文档会有所帮助。我忽略了
func Benchmark(f func(b *B)) BenchmarkResult
这将 运行 我的功能根本不需要任何测试工具。
Benchmark benchmarks a single function. Useful for creating custom
benchmarks that do not use the "go test" command.
这样我就可以遍历我的测试用例并为每种可能性创建一个函数,然后直接 运行 基准。
我有几个不同的接口实现,以及我想测试它们的各种因素。最终目标是为不同情况下的不同实施制作结果网格。
我可以为每种可能的组合编写测试,但这会让人筋疲力尽:
func Benchmark_ImplA_N100_X300(b *testing.B){
impl := newImplA(100,300)
runBenchmark(b,impl)
}
我添加的组合越多,我就越需要copy/paste。这很快就会变得很麻烦。
我很想做这样的事情:
tests := []testing.InternalBenchmark{}
for _, n := range []int{50,100,500,10000}{
for _,x := range []int{300,200}{
for name, gen := range implementations{
impl = gen(n,x)
bm := testing.InternalBenchmark{Name: fmt.Sprint(name,x,n)}
bm.F = func(b *testing.B){
runBench(b,impl)
}
tests = append(tests,bm)
}
}
}
testing.RunBenchmarks(anything, tests)
这样我就可以更新组合列表,基准测试会神奇地 运行 所有组合。我在 main 和 TestMain 中尝试过类似的东西,但没有任何输出。我不确定是我用错了,还是测试包只是在做一些有趣的事情。
我真的不在乎 go test
工具是否可以处理它或者是否有其他方法。
是的,这是可能的。在您的测试文件 (xxx_test.go
) 中创建您自己的 TestMain()
函数,并在组装动态基准用例(结构 testing.InternalBenchmark
), call testing.Main()
which properly parses command line flags, creates and sets up testing.M
, and prepares and calls testing.RunBenchmarks()
的值)后在其中创建函数。这样您的动态基准仍将是 运行由 go test
.
注意:testing.Main()
永远不会 return 因为它调用 os.Exit()
. If you want to perform further logging and calculations on the benchmark results, you may also call testing.MainStart()
.Run()
(which is what testing.Main()
does), and you may pass the exit code which is returned by M.Run()
到 os.Exit()
。
下面是一个完整的测试文件,您可以简单地 运行 和 go test -bench .
。
输出是:动态生成测试的基准测试结果(具有不同参数的不同实现):
testing: warning: no tests to run
PASS
main.EngineA[impl=0, n=50, x=300]-4 100000 16716 ns/op
main.EngineB[impl=1, n=50, x=300]-4 100000 24788 ns/op
main.EngineA[impl=0, n=50, x=200]-4 100000 10764 ns/op
main.EngineB[impl=1, n=50, x=200]-4 100000 16415 ns/op
main.EngineA[impl=0, n=100, x=300]-4 50000 33426 ns/op
main.EngineB[impl=1, n=100, x=300]-4 30000 48466 ns/op
main.EngineA[impl=0, n=100, x=200]-4 50000 20452 ns/op
main.EngineB[impl=1, n=100, x=200]-4 50000 33134 ns/op
main.EngineA[impl=0, n=500, x=300]-4 10000 163087 ns/op
main.EngineB[impl=1, n=500, x=300]-4 5000 238043 ns/op
main.EngineA[impl=0, n=500, x=200]-4 10000 102662 ns/op
main.EngineB[impl=1, n=500, x=200]-4 10000 163113 ns/op
main.EngineA[impl=0, n=1000, x=300]-4 5000 319744 ns/op
main.EngineB[impl=1, n=1000, x=300]-4 3000 512077 ns/op
main.EngineA[impl=0, n=1000, x=200]-4 10000 201036 ns/op
main.EngineB[impl=1, n=1000, x=200]-4 5000 325714 ns/op
ok _/xxx/src/play 27.307s
和来源(测试文件,例如dynbench_test.go
):
package main
import (
"fmt"
"testing"
)
type Engine interface {
Calc()
}
type EngineA struct{ n, x int }
func (e EngineA) Calc() {
for i := 0; i < e.n; i++ {
a, b := make([]byte, e.x), make([]byte, e.x)
copy(b, a)
}
}
type EngineB struct{ n, x int }
func (e EngineB) Calc() {
for i := 0; i < e.n*2; i++ {
a, b := make([]byte, e.x/2), make([]byte, e.x/2)
copy(b, a)
}
}
func TestMain(m *testing.M) {
implementations := [](func(n, x int) Engine){
func(n, x int) Engine { return EngineA{n, x} },
func(n, x int) Engine { return EngineB{n, x} },
}
benchmarks := []testing.InternalBenchmark{}
for _, n := range []int{50, 100, 500, 1000} {
for _, x := range []int{300, 200} {
for name, gen := range implementations {
impl := gen(n, x)
bm := testing.InternalBenchmark{
Name: fmt.Sprintf("%T[impl=%d, n=%d, x=%d]", impl, name, n, x)}
bm.F = func(b *testing.B) {
for i := 0; i < b.N; i++ {
impl.Calc()
}
}
benchmarks = append(benchmarks, bm)
}
}
}
anything := func(pat, str string) (bool, error) { return true, nil }
testing.Main(anything, nil, benchmarks, nil)
}
注释 #2:
testing.Main()
、testing.MainStart()
和 testing.InternalBenchmark
可能会在 Go 的未来版本中更改(或删除):
An internal function / internal type but exported because it is cross-package; part of or called by the implementation of the "go test" command.
我想阅读文档会有所帮助。我忽略了
func Benchmark(f func(b *B)) BenchmarkResult
这将 运行 我的功能根本不需要任何测试工具。
Benchmark benchmarks a single function. Useful for creating custom benchmarks that do not use the "go test" command.
这样我就可以遍历我的测试用例并为每种可能性创建一个函数,然后直接 运行 基准。