go benchmark 性能测试, 基准测试, 单元测试, 覆盖测试
编写基准测试
func BenchmarkSprintf(b *testing.B){
num:=10
b.ResetTimer()
for i:=0;i<b.N;i++{
fmt.Sprintf("%d",num)
}
}
// 加上 -bench= 标记,接受一个表达式作为参数, .表示运行所有的基准测试
// -run=匹配一个从来没有的单元测试方法,过滤掉单元测试的输出,我们这里使用none
// 也可以使用 -run=^$, 匹配这个规则的
go test -bench=. -run=none
go test -bench=. -run=^$
并发基准测试
func BenchmarkCombinationParallel(b *testing.B) {
// 测试一个对象或者函数在多线程的场景下面是否安全
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
m := rand.Intn(100) + 1
n := rand.Intn(m)
combination(m, n)
}
})
}
性能对比
func BenchmarkSprintf(b *testing.B){
num:=10
b.ResetTimer()
for i:=0;i<b.N;i++{
fmt.Sprintf("%d",num)
}
}
func BenchmarkFormat(b *testing.B){
num:=int64(10)
b.ResetTimer()
for i:=0;i<b.N;i++{
strconv.FormatInt(num,10)
}
}
func BenchmarkItoa(b *testing.B){
num:=10
b.ResetTimer()
for i:=0;i<b.N;i++{
strconv.Itoa(num)
}
}
➜ hello go test -bench=. -run=none
BenchmarkSprintf-8 20000000 117 ns/op
BenchmarkFormat-8 50000000 33.3 ns/op
BenchmarkItoa-8 50000000 34.9 ns/op
PASS
ok flysnow.org/hello 5.951s
从结果上看strconv.FormatInt函数是最快的,其次是strconv.Itoa,然后是fmt.Sprintf最慢,前两个函数性能达到了最后一个的3倍多。那么最后一个为什么这么慢的,我们再通过-benchmem找到根本原因。
➜ hello go test -bench=. -benchmem -run=none
BenchmarkSprintf-8 20000000 110 ns/op 16 B/op 2 allocs/op
BenchmarkFormat-8 50000000 31.0 ns/op 2 B/op 1 allocs/op
BenchmarkItoa-8 50000000 33.1 ns/op 2 B/op 1 allocs/op
PASS
ok flysnow.org/hello 5.610s
-benchmem可以提供每次操作分配内存的次数,以及每次操作分配的字节数。从结果我们可以看到,性能高的两个函数,每次操作都是进行1次内存分配,而最慢的那个要分配2次;性能高的每次操作分配2个字节内存,而慢的那个函数每次需要分配16字节的内存。从这个数据我们就知道它为什么这么慢了,内存分配都占用都太高。
在代码开发中,对于我们要求性能的地方,编写基准测试非常重要,这有助于我们开发出性能更好的代码。不过性能、可用性、复用性等也要有一个相对的取舍,不能为了追求性能而过度优化。
结合 pprof
package bench
import "testing"
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
func BenchmarkFib10(b *testing.B) {
// run the Fib function b.N times
for n := 0; n < b.N; n++ {
Fib(10)
}
}
go test -bench=. -benchmem -cpuprofile profile.out
还可以同时看内存
go test -bench=. -benchmem -memprofile memprofile.out -cpuprofile profile.out
然后就可以用输出的文件使用pprof
go tool pprof profile.out
File: bench.test
Type: cpu
Time: Apr 5, 2018 at 4:27pm (EDT)
Duration: 2s, Total samples = 1.85s (92.40%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1.85s, 100% of 1.85s total
flat flat% sum% cum cum%
1.85s 100% 100% 1.85s 100% bench.Fib
0 0% 100% 1.85s 100% bench.BenchmarkFib10
0 0% 100% 1.85s 100% testing.(*B).launch
0 0% 100% 1.85s 100% testing.(*B).runN
这个是使用cpu 文件, 也可以使用内存文件
然后你也可以用list命令检查函数需要的时间
(pprof) list Fib
1.84s 2.75s (flat, cum) 148.65% of Total
. . 1:package bench
. . 2:
. . 3:import "testing"
. . 4:
530ms 530ms 5:func Fib(n int) int {
260ms 260ms 6: if n < 2 {
130ms 130ms 7: return n
. . 8: }
920ms 1.83s 9: return Fib(n-1) + Fib(n-2)
. . 10:}
或者使用web命令生成图像(png,pdf,...)
brew install graphviz
火焰图
火焰图(Flame Graph)是 Bredan Gregg 创建的一种性能分析图表,因为它的样子近似火焰而得名。
火焰图 svg 文件可以通过浏览器打开,它对于调用图的最优点是它是动态的:可以通过点击每个方块来 zoom in 分析它上面的内容。
火焰图的调用顺序从下到上,每个方块代表一个函数,它上面一层表示这个函数会调用哪些函数,方块的大小代表了占用 CPU 使用的长短。火焰图的配色并没有特殊的意义,默认的红、黄配色是为了更像火焰而已。
runtime/pprof分析项目, 会在当前文件夹内导出profile文件。然后用火焰图去分析,就不能指定域名了,要指定文件。
go-torch
网上介绍大部分使用uber的开源工具
go-torch。这是 uber 开源的一个工具,可以直接读取 golang profiling 数据,并生成一个火焰图的 svg 文件。
go-torch 工具的使用非常简单,没有任何参数的话,它会尝试从 http://localhost:8080/debug/pprof/profile 获取 profiling 数据。它有三个常用的参数可以调整:
-u --url:要访问的 URL,这里只是主机和端口部分
-s --suffix:pprof profile 的路径,默认为 /debug/pprof/profile
--seconds:要执行 profiling 的时间长度,默认为 30s
原生支持
从 Go 1.11 开始, 火焰图被集成进入 Go 官方的 pprof 库.
# This will listen on :8081 and open a browser.
# Change :8081 to a port of your choice.
$ go tool pprof -http=":8081" [binary] [profile]
一个web 小例子
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"time"
)
func sayHelloHandler(w http.ResponseWriter, r *http.Request) {
hellowold(10000)
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Fprintf(w, "Hello world!\n") //这个写入到w的是输出到客户端的
}
func main() {
http.HandleFunc("/", sayHelloHandler) // 设置访问路由
log.Fatal(http.ListenAndServe(":8080", nil))
}
func hellowold(times int) {
time.Sleep(time.Second)
var counter int
for i := 0; i < times; i++ {
for j := 0; j < times; j++ {
counter++
}
}
}
使用下面的命令开启监控,然后访问几次localhost:8080
go tool pprof -http=":8081" http://localhost:8080/debug/pprof/profile
过一会儿会产生个web窗口, 选择 VIEW->Flame Graph 得到火焰图形
http://localhost:8081/ui/flamegraph
Testing flags
go 测试后面可以跟哪些参数
Testing flags
常用flag
-bench regexp:性能测试,支持表达式对测试函数进行筛选。-bench .则是对所有的benchmark函数测试
-benchmem:性能测试的时候显示测试函数的内存分配的统计信息
-count n:运行测试和性能多少此,默认一次
-run regexp:只运行特定的测试函数, 比如-run ABC只测试函数名中包含ABC的测试函数
-timeout t:测试时间如果超过t, panic,默认10分钟
-v:显示测试的详细信息,也会把Log、Logf方法的日志显示出来
go test -v -bench=. -benchmem main_test.go
go test -v -bench=BenchmarkTrie -benchmem -run=none ./
go test -bench=. -benchmem -memprofile memprofile.out -cpuprofile profile.out example_test.go
go tool pprof -http=":8081" profile.out
覆盖测试
-
测试覆盖率就是测试运行到的被测试代码的代码数目。其中以语句的覆盖率最为简单和广泛,语句的覆盖率指的是在测试中至少被运行一次的代码占总代码数的比例。
-
测试整个包: go test -cover=true pkg_name
-
测试单个测试函数: go test -cover=true pkg_name -run TestSwap。
-
生成 HTML 报告
- go test -cover=true pkg_name -coverprofile=out.out 将在当前目录生成覆盖率数据
- 配合 go tool cover -html=out.out 在浏览器中打开 HTML 报告。
- 或者使用 go tool cover -html=out.out -o=out.html 生成 HTML 文件。
批量收集go pkg覆盖测试数据
#!/bin/bash
set -e
profile="cover.out"
htmlfile="cover.html"
mergecover="merge_cover"
mode="set"
for package in $(go list ./... | grep -v src); do
coverfile="$(echo $package | tr / -).cover"
go test -covermode="$mode" -coverprofile="$coverfile" -coverpkg="$package" "$package"
done
# merge all profiles
grep -h -v "^mode:" *.cover | sort > $mergecover
# aggregate duplicated code-block data
echo "mode: $mode" > $profile
current=""
count=0
while read line; do
block=$(echo $line | cut -d ' ' -f1-2)
num=$(echo $line | cut -d ' ' -f3)
if [ "$current" == "" ]; then
current=$block
count=$num
elif [ "$block" == "$current" ]; then
count=$(($count + $num))
else
echo $current $count >> $profile
current=$block
count=$num
fi
done < $mergecover
if [ "$current" != "" ]; then
echo $current $count >> $profile
fi
# save result
go tool cover -html=$profile -o $htmlfile
go tool cover -func=$profile
rm $mergecover
rm $profile
rm *.cover
|
请发表评论