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

时间:2019-04-22 05:07:12

标签: go testing benchmarking

在测试从API调用的数据库过程时,当它顺序运行时,它似乎在〜3s内一致地运行。但是,我们注意到,当同时发送多个请求时,这可能需要更长的时间,从而导致超时。我正在尝试将“一次几次请求”的情况重现为go test

我尝试了-parallel 10进入测试标志,但是the timings大约在28秒时就结束了。

我的benchmark function出问题了吗?

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)
            })
    }
}

否则我将如何实现自己的追求?

5 个答案:

答案 0 :(得分:4)

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

引用Command go: Testing flags:

-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循环启动n goroutine,它们都尝试调用可测试服务。这样做的一个问题是,这只能确保开始时n个并发的goroutine,因为随着调用开始完成,其余调用的并发性将越来越少。

要克服此问题并真正测试n并发调用,您应该有一个包含n个工作人员的工作人员池,并不断向该工作人员池提供作业,确保会有n并发服务调用始终。有关工作池的实现,请参见Is this an idiomatic worker thread pool in Go?

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

还要注意,仅出于测试目的,甚至可能不需要工作池。您可以只使用循环来启动n goroutine,但是要确保每个启动的goroutine都会继续调用该服务,并且在一次调用后不会返回。

答案 1 :(得分:2)

您的示例代码混合了几件事。为什么在那里使用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个goroutine将不会并行运行。因此,请确保您有足够的可用核心。

从此开始,您可以玩一点。您可以开始在基准测试中调用此函数。您还可以尝试使用goroutine的数量。

答案 2 :(得分:1)

如文档所示,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;在调用基准测试函数之前,计时器不会启动,因此将其作为函数中的第一个操作调用是没有意义的。它适用于您不想在基准测试结果之前包含在基准测试循环之前进行耗时设置的情况。我也删除了断言,因为这是一个基准,而不是测试;断言是为了在测试中进行有效性检查,仅用于甩掉基准测试中的计时结果。

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

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

如上所述,-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: %v\n", time.Now().Unix())
  time.Sleep(10 * time.Second)
  fmt.Printf("Current Unix Time: %v\n", 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>
}

然后只需进行测试,您应该会看到:

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

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