并行运行基准测试,即模拟并发请求



当测试从 API 调用的数据库过程时,当它按顺序运行时,它似乎在 ~3 秒内一致运行。但是,我们注意到,当多个请求同时传入时,这可能需要更长的时间,从而导致超时。我正在尝试将"一次多个请求"案例重现为go test.

我尝试了-parallel 10go 测试标志,但时间在 ~28 秒时相同。

我的基准测试函数有问题吗?

func Benchmark_RealCreate(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
name := randomdata.SillyName()
r := gofight.New()
u := []unit{unit{MefeUnitID: name, MefeCreatorUserID: "user", BzfeCreatorUserID: 55, ClassificationID: 2, UnitName: name, UnitDescriptionDetails: "Up on the hills and testing"}}
uJSON, _ := json.Marshal(u)
r.POST("/create").
SetBody(string(uJSON)).
Run(h.BasicEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Contains(b, r.Body.String(), name)
assert.Equal(b, http.StatusOK, r.Code)
})
}
}

否则我如何才能实现我所追求的?

-parallel标志不适用于在多个实例中运行相同的测试或基准并行。

引用命令 go: 测试标志:

-parallel n
Allow parallel execution of test functions that call t.Parallel.
The value of this flag is the maximum number of tests to run
simultaneously; by default, it is set to the value of GOMAXPROCS.
Note that -parallel only applies within a single test binary.
The 'go test' command may run tests for different packages
in parallel as well, according to the setting of the -p flag
(see 'go help build').

因此,基本上,如果您的测试允许,您可以使用-parallel并行运行多个不同的测试或基准测试函数,但在多个实例中不是相同的测试或基准测试函数。

通常,并行运行多个基准测试函数会破坏对函数进行基准测试的目的,因为在多个实例中并行运行它通常会扭曲基准测试。

但是,在你的情况下,代码效率不是你想要衡量的,你想衡量一个外部服务。因此,go 的内置测试和基准测试工具并不真正合适。

当然,当我们的其他测试和基准测试运行时,我们仍然可以使用让这个"基准测试"自动运行的便利,但你不应该把它强加到传统的基准测试框架中。

首先想到的是使用 for 循环来启动ngoroutine,这些 goroutines 都尝试调用可测试的服务。这样做的一个问题是,这只能确保开始时n并发 goroutine,因为随着调用开始完成,剩余的并发性会越来越少。

要克服此问题并真正测试并发调用n,您应该拥有一个包含n个工作线程的工作线程池,并不断将作业馈送到此工作线程池,确保始终存在n并发服务调用。有关工作线程池实现,请参阅 Go 中的惯用工作线程池吗?

总而言之,启动一个有n个工人的工人池,让一个goroutine在任意时间内(例如30秒或1分钟)向其发送作业,并测量(计数)已完成的作业。基准测试结果将是一个简单的划分。

另请注意,仅出于测试目的,甚至可能不需要辅助角色池。你可以只使用循环来启动ngoroutines,但要确保每个启动的 goroutine 都会继续调用服务,并且在一次调用后不会返回。

我是新手,但你为什么不尝试创建一个函数并使用标准并行测试运行它呢?

func Benchmark_YourFunc(b *testing.B) {
b.RunParralel(func(pb *testing.PB) {
for pb.Next() {
YourFunc(staff ...T)
}
})
}

您的示例代码混合了多种内容。你为什么在那里使用assert?这不是测试,而是基准。如果assert方法很慢,那么您的基准将是。

您还将并行执行从代码中移到了测试命令中。应尝试使用并发发出并行请求。这里只是一种可能如何开始:

func executeRoutines(routines int) {
wg := &sync.WaitGroup{}
wg.Add(routines)
starter := make(chan struct{})
for i := 0; i < routines; i++ {
go func() {
<-starter
// your request here
wg.Done()
}()
}
close(starter)
wg.Wait()
}

https://play.golang.org/p/ZFjUodniDHr

我们在这里启动一些 goroutine,它们正在等待starter关闭。因此,您可以直接在该行之后设置请求。该函数等到所有请求完成,我们正在使用WaitGroup。

但重要的是:Go 只支持并发。因此,如果您的系统没有 10 个内核,则 10 个 goroutines 将不会并行运行。因此,请确保您有足够的可用内核。

有了这个开始,你可以玩一点。您可以开始在基准测试中调用此函数。你也可以玩弄goroutine的数量。

如文档所示,parallel标志是允许并行运行多个不同的测试。您通常不希望并行运行基准测试,因为这会同时运行不同的基准测试,从而抛弃所有基准测试的结果。如果要对并行流量进行基准测试,则需要将并行流量生成写入测试中。您需要决定这应该如何与b.N哪个是您的工作因素一起工作;我可能会将其用作总请求计数,并编写一个或多个基准测试来测试不同的并发负载级别,例如:

func Benchmark_RealCreate(b *testing.B) {
concurrencyLevels := []int{5, 10, 20, 50}
for _, clients := range concurrencyLevels {
b.Run(fmt.Sprintf("%d_clients", clients), func(b *testing.B) {
sem := make(chan struct{}, clients)
wg := sync.WaitGroup{}
for n := 0; n < b.N; n++ {
wg.Add(1)
go func() {
name := randomdata.SillyName()
r := gofight.New()
u := []unit{unit{MefeUnitID: name, MefeCreatorUserID: "user", BzfeCreatorUserID: 55, ClassificationID: 2, UnitName: name, UnitDescriptionDetails: "Up on the hills and testing"}}
uJSON, _ := json.Marshal(u)
sem <- struct{}{}
r.POST("/create").
SetBody(string(uJSON)).
Run(h.BasicEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {})
<-sem
wg.Done()
}()
}
wg.Wait()
})
}
}

请注意,这里我删除了初始ResetTimer;计时器在调用基准测试函数之前不会启动,因此将其称为函数中的第一个操作毫无意义。它适用于在基准测试循环之前进行耗时的设置,而您不希望包含在基准测试结果中的情况。我还删除了断言,因为这是一个基准,而不是测试;断言用于测试中的有效性检查,仅用于抛弃基准测试中的计时结果。

一件事是基准测试(测量代码运行所需的时间),另一件事是负载/压力测试。

如上所述的 -parallel 标志是允许一组测试并行执行,允许测试集执行得更快,而不是并行执行某些测试 N 次。

但很容易实现您想要的(执行相同的测试N次)。下面是一个非常简单(非常快速和肮脏)的例子,只是为了澄清/演示要点,它完成了这个非常具体的情况:

  • 定义一个测试并将其标记为并行执行 => TestAverage,并调用 t.Parallel
  • 然后,定义另一个测试,并使用 RunParallel 执行所需的测试实例数 (TestAverage)。

要测试的类:

package math
import (
"fmt"
"time"
)
func Average(xs []float64) float64 {
total := float64(0)
for _, x := range xs {
total += x
}
fmt.Printf("Current Unix Time: %vn", time.Now().Unix())
time.Sleep(10 * time.Second)
fmt.Printf("Current Unix Time: %vn", time.Now().Unix())
return total / float64(len(xs))
}

测试功能:

package math
import "testing"
func TestAverage(t *testing.T) {
t.Parallel()
var v float64
v = Average([]float64{1,2})
if v != 1.5 {
t.Error("Expected 1.5, got ", v)
}
}
func TestTeardownParallel(t *testing.T) {
// This Run will not return until the parallel tests finish.
t.Run("group", func(t *testing.T) {
t.Run("Test1", TestAverage)
t.Run("Test2", TestAverage)
t.Run("Test3", TestAverage)
})
// <tear-down code>
}

然后只需做一个 go 测试,你应该看到:

X:>go test
Current Unix Time: 1556717363
Current Unix Time: 1556717363
Current Unix Time: 1556717363

10秒后

...
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717383
PASS
ok      _/X_/y        20.259s

最后,额外的两行是因为TestAverage也被执行了。

有趣的是:如果你从 TestAverage 中删除 t.Parallel(),它将全部按顺序执行:

X:> go test
Current Unix Time: 1556717564
Current Unix Time: 1556717574
Current Unix Time: 1556717574
Current Unix Time: 1556717584
Current Unix Time: 1556717584
Current Unix Time: 1556717594
Current Unix Time: 1556717594
Current Unix Time: 1556717604
PASS
ok      _/X_/y        40.270s

当然,这可以变得更加复杂和可扩展......

相关内容

  • 没有找到相关文章

最新更新