英文:
Is it possible to dynamically run benchmark tests?
问题
我有几种不同的接口实现,并且想在各种情况下对它们进行测试。最终目标是为不同实现在不同情况下生成一个结果网格。
我可以为每种可能的组合编写一个测试,但这样做会很繁琐:
func Benchmark_ImplA_N100_X300(b *testing.B){
impl := newImplA(100,300)
runBenchmark(b,impl)
}
随着组合的增加,我需要复制/粘贴的次数也越多。这很快变得麻烦。
我希望能够做类似这样的事情:
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
工具是否能处理它,或者是否有其他方法。
英文:
I have a few different implementations of an interface, and a variety of factors I want to test them under. End goal being to make a grid of results for different implementations in different situations.
I could write a test for each possible combination, but that gets exhausting:
func Benchmark_ImplA_N100_X300(b *testing.B){
impl := newImplA(100,300)
runBenchmark(b,impl)
}
The more combinations I add, the more I have to copy/paste. This gets cumbersome fast.
I would love to do something like:
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)
so that I can update the list(s) of combinations, and benchmarks will magically run in all combinations. I have tries something like this in main and in TestMain, and nothing ever outputs. I am not sure if I am using it wrong, or if the testing package is just doing something funny.
I really don't care if the go test
tool can handle it or if there is some other way.
答案1
得分: 5
是的,这是可能的。在你的测试文件(xxx_test.go
)中创建自己的TestMain()
函数,在其中组装动态基准测试案例(testing.InternalBenchmark
结构的值),然后调用testing.Main()
函数,该函数会正确解析命令行标志,创建并设置testing.M
,准备并调用testing.RunBenchmarks()
。这样,你的动态基准测试仍然可以通过go test
命令运行。
注意:testing.Main()
函数永远不会返回,因为它调用了os.Exit()
。如果你想对基准测试结果进行进一步的日志记录和计算,你还可以调用testing.MainStart()
.Run()
函数(这是testing.Main()
函数所做的),并将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版本中发生更改(或被移除):
> 这是一个内部函数/内部类型,但由于它是跨包的,所以被导出;它是“go test”命令的实现的一部分或被其调用。
英文:
Yes, it's possible. In your test file (xxx_test.go
) create your own TestMain()
function, and inside that after assembling the dynamic benchmark cases (values of struct testing.InternalBenchmark
), call testing.Main()
which properly parses command line flags, creates and sets up testing.M
, and prepares and calls testing.RunBenchmarks()
. This way your dynamic benchmarks will still be runnable by go test
.
Notes: testing.Main()
will never return as it calls 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()
to os.Exit()
.
Below comes a complete test file which you can simply run with go test -bench .
.
The output is: benchmark results of dynamically generated tests (of different implementations with different parameters):
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
And the source (a test file, e.g. 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)
}
Notes #2:
testing.Main()
, testing.MainStart()
and testing.InternalBenchmark
may change (or get removed) in a future release of 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.
答案2
得分: 2
我想阅读文档会有所帮助。我忽略了func Benchmark(f func(b *B)) BenchmarkResult
,它可以在不需要任何测试工具的情况下运行我的函数。
> Benchmark用于对单个函数进行基准测试。适用于创建不使用"go test"命令的自定义基准测试。
这样,我可以遍历我的测试用例,并为每种可能性创建一个函数,然后直接运行基准测试。
英文:
I suppose reading the docs would help. I overlooked
func Benchmark(f func(b *B)) BenchmarkResult
which will run my function wothout need for any test harness at all.
> Benchmark benchmarks a single function. Useful for creating custom
> benchmarks that do not use the "go test" command.
That way I can iterate through my test cases and create a function for each possibility, then run the benchmark directly.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论