忘川彼岸|如果你只知道 go test 用于单元测试就太 LOW 了


忘川彼岸|如果你只知道 go test 用于单元测试就太 LOW 了“A Journey With Go” 专属插图 , 由 Renee French 根据原始 Go Gopher 制作 。
?? 本文基于 Go 1.13.
go test 命令提供了许多出色的功能 , 比如代码覆盖率 , CPU 和 内存分析 。 要提供这些统计信息 , Go 就需要一种方式来跟踪 CPU 使用率 , 或在代码覆盖中跟踪一个函数何时被用到 。
性能测量Go 使用多种方式来产生这些统计信息:

  • 动态插入性能测量语句 , 使其可以跟踪到代码何时进入一个函数或条件 。 这个策略在代码覆盖率[1]中使用 。
  • 每秒记录多次程序样本 。 这个策略在CPU 分析[2]中用到 。
  • 在代码中使用静态 hook , 以便在执行期间调用所需函数 。 这个策略在内存分析中用到 。
我们来写一个简单的程序并回顾所有内容 。 这是我们在后面的章节将使用的代码:
package mainimport "math/rand"func main() { println(run())}//go:noinlinefunc run() int { a := 0 for i:= 0; i < rand.Intn(100000); i++ {if i % 2 == 0 {add(&a)} else {sub(&a)} } return a}//go:noinlinefunc add(a *int) { *a += rand.Intn(10)}//go:noinlinefunc sub(a *int) { *a -= rand.Intn(10)}main.go 托管在 [github] () 查看[3]
代码覆盖率通过 GOSSAFUNC=run Go test -cover 命令生成的 SSA 代码 , 我们可以查看 Go 对程序进行了什么样的修改:
忘川彼岸|如果你只知道 go test 用于单元测试就太 LOW 了变量 GoCover_0_313837343662366134383538 是一个标志数组 , 其中每个键是一个代码块 , 当代码实际进入这一块时对应的标志设置为 1.
你可以在我的文章 “Go: Compiler Phases”[4] 中找到更多关于 SSA 的信息 。
生成的代码将稍后在管理代码覆盖率报告的函数中使用 。 我们可以通过使用objdump 命令反汇编代码覆盖期间生成的目标文件来进行验证 。 运行 go test -cover -o main.o && Go tool objdump main.go 将反汇编代码并显示缺少的部分 。 它首先初始化并在自动生成的 init 函数中注册 coverage:
忘川彼岸|如果你只知道 go test 用于单元测试就太 LOW 了test.go 添加的 init 方法
然后 , 如前所述 , 测试将在执行期间收集覆盖率数据并且会触发一个方法来实际写入和显示覆盖率:
忘川彼岸|如果你只知道 go test 用于单元测试就太 LOW 了go test 调用的 after 函数
CPU 分析跟踪 CPU 使用率的策略则有所不同 。 Go 会停止程序并收集正在运行程序的样本 。 这里是未开启 CPU 分析的代码的 trace:
忘川彼岸|如果你只知道 go test 用于单元测试就太 LOW 了这里是相同代码开启了 CPU 分析的 trace:
忘川彼岸|如果你只知道 go test 用于单元测试就太 LOW 了增加的 trace 与 pprof 及性能分析相关 。 这里是其中一个的放大图:
忘川彼岸|如果你只知道 go test 用于单元测试就太 LOW 了profileWriter 方法将循环调用 , 每 100 毫秒收集 CPU 数据 , 以在性能分析结束时最终生成报告 。


推荐阅读