跳转到主要内容

在本文中,我们将看看基准测试。更具体地说,我们将研究如何对基于 Go 的程序进行基准测试。

在性能很重要的时候,能够对程序的执行方式进行基准测试并分析潜在的瓶颈在哪里,真的很有价值。通过了解这些瓶颈所在的位置,我们可以更有效地确定将工作重点放在哪里,以提高我们系统的性能。

注意 - 需要注意的是,性能调整通常应在系统启动并运行后进行。

“过早的优化是万恶之源”——Donald Knuth

在本教程中,我们将研究如何为非常简单的函数执行标准基准测试,然后转向更高级的示例,最后研究如何生成看起来很酷的火焰图。

先决条件

 

  • 您需要在开发机器上安装 Go 版本 1.11+。

一个简单的基准测试


在 Go 中,基准测试可以与标准单元测试一起编写。这些基准函数应以“Benchmark”为前缀,后跟函数名称,与您为测试函数添加前缀 Test 的方式相同。

让我们从我们之前的测试文章中获取我们的代码,并尝试为此编写一个简单的基准函数。创建一个名为 main.go 的新文件并将以下代码添加到该文件中:

package main

import (
    "fmt"
)

// Calculate returns x + 2.
func Calculate(x int) (result int) {
    result = x + 2
    return result
}

func main() {
    fmt.Println("Hello World")
}

正如你所看到的,这并不疯狂,我们有一个计算函数,它接收一个 int 值,将该值加 2 并返回它。这是用于创建我们的第一个基准测试的完美功能。

因此,为了为我们的Calculate函数编写基准测试,我们可以利用部分测试包并在main_test.go中编写一个函数。

package main

import (
    "testing"
)

func BenchmarkCalculate(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Calculate(2)
    }
}

为了让我们运行我们的新基准,我们可以简单地运行 go test -bench=。它将为我们运行我们软件包中的所有基准测试。这应该返回如下内容:

Elliots-MBP:go-testing-tutorial elliot$ go test -bench=.
goos: darwin
goarch: amd64
BenchmarkCalculate-8    2000000000               0.30 ns/op
PASS
ok      _/Users/elliot/Documents/Projects/tutorials/golang/go-testing-tutorial  0.643s


如您所见,它以每次操作 0.30 ns 的速度运行了我们的 Calculate() 函数 2,000,000,000 次。整个基准测试仅需 0.653 秒即可通过。

显然,随着更多基准测试添加到我们的套件中,或者我们功能的复杂性增加,您应该会看到这些基准测试花费的时间越来越长。

-run 标志


在上面的示例中,我们结合测试运行了基准测试。如果您拥有大量测试套件并且只想验证性能改进,这可能并不理想。如果你想指定你只想运行你的基准测试,那么你可以使用 -run 标志。

这个 -run 标志采用正则表达式模式,可以过滤掉我们想要运行的基准。假设我们的 main_test.go 文件中有 2 个其他测试函数。

package main

import (
    "fmt"
    "testing"
)

func TestCalculate(t *testing.T) {
    fmt.Println("Test Calculate")
    expected := 4
    result := Calculate(2)
    if expected != result {
        t.Error("Failed")
    }
}

func BenchmarkCalculate(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Calculate(2)
    }
}

func TestOther(t *testing.T) {
    fmt.Println("Testing something else")
    fmt.Println("This shouldn't run with -run=calc")
}

如果我们只想运行我们的计算测试,我们可以指定一个匹配计算的正则表达式:

$ go test -run=Calculate -bench=.


这将触发我们的 TestCalculate 和 BenchmarkCalculate 函数:

go test -run=Calc -bench=.
Test Calculate
goos: darwin
goarch: amd64
BenchmarkCalculate-8    2000000000               0.28 ns/op
PASS
ok      _/Users/elliot/Documents/Projects/Tutorialedge/go-benchmarking-tutorial 0.600s


如果我们只想运行 BenchmarkCalculate 函数,那么我们可以将正则表达式模式更改为:

go test -run=Bench -bench=.
goos: darwin
goarch: amd64
BenchmarkCalculate-8    2000000000               0.29 ns/op
PASS
ok      _/Users/elliot/Documents/Projects/Tutorialedge/go-benchmarking-tutorial 0.616s


你会在上面的输出中看到只有我们的 BenchmarkCalculate 函数被触发了。只要我们为基准函数保持一致的命名约定,指定一个只测试它们的命令应该是相当容易的。

增加复杂性。


通常,您需要使用各种不同的输入对您的程序进行基准测试。您希望在许多不同的真实场景下测量程序的性能特征。

我们将使用上一个示例中的计算函数,这次我们将添加一组不同的基准来测试我们函数的各种不同输入:

package main

import (
    "testing"
)

func benchmarkCalculate(input int, b *testing.B) {
    for n := 0; n < b.N; n++ {
        Calculate(input)
    }
}

func BenchmarkCalculate100(b *testing.B)         { benchmarkCalculate(100, b) }
func BenchmarkCalculateNegative100(b *testing.B) { benchmarkCalculate(-100, b) }
func BenchmarkCalculateNegative1(b *testing.B)   { benchmarkCalculate(-1, b) }

因此,我们在这里创建了 3 个不同的 Benchmark 函数,它们调用 benchmarkCalculate() 并使用各种不同类型的输入。这让我们可以看到不同输入之间是否存在任何性能差异,并让我们更准确地了解我们的程序在现实生活中的表现。

go-benchmarking-tutorial go test -run=Bench -bench=.
goos: darwin
goarch: amd64
BenchmarkCalculate100-8                 2000000000               0.29 ns/op
BenchmarkCalculateNegative100-8         2000000000               0.29 ns/op
BenchmarkCalculateNegative1-8           2000000000               0.29 ns/op
PASS
ok      _/Users/elliot/Documents/Projects/Tutorialedge/go-benchmarking-tutorial 1.850s


在编写基准套件时,值得充实这样的多个基准,以便为您提供更准确的表示。

结论


希望这篇文章能够为您提供一些关于如何实施自己的基准测试的指示。如果您需要进一步的帮助,请在下面的评论部分告诉我!

文章链接