Go测试-benchmark测试
benchmark测试入门
- 测试以Bnechmark为方法开头
- 运行测试的时候,形如普通的测试,但是需要加上-bench 选项
- 运行选项:
- -bench 选项:接受一个正则表达式,匹配上的才会执行
- -benchmem:输出内存分配
- -benchtime:运行时间,默认1s。可以是时间,也可以是次数,例如1s,2m,500x
- -count:轮数,执行多少轮
- 生成profile文件,用于pprof工具分析:
- -cpuprofile=cpu.out
- -memprofile=mem.out
- -blockfile=block.out
- 运行结果主要包括:运行次数、单次运行耗时、总耗时
主函数代码:
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
测试函数代码:
func BenchmarkFib(t *testing.B) {
Fib(40)
}
输出:
内存和CPU分析
工具
使用默认的pprof工具:go tool pprof、cpu.prof
- 图形界面依赖于graphviz
- 常用命令(一般建议优先使用web调出图形化界面进行分析):
- top:列出消耗最高的调用
- list:列出问题代码片段
- peek:查询具体函数的调用关系
- web:图形化界面
google pprof工具
- 使用pprof 工具来分析:go get-u github.com/google/pprof
- 安装graphviz
- web界面:pprof -http=:8080 cpu.prof
调用链路图
- 在文件生成的图中,中间标红的就是关键路径,或者说瓶颈所在的路径
- 在一般的企业级应用中,这条路径都是因为内存分配引起的
- 除了关键路径,一些开销比较大的分支路径也要关心
- 优化关键路径性价比最高(可以做到数量级提升)。但是有时候关键路径优化不动,那么可以尝试优化其它分支路径
火焰图
长度代表的就是资源消耗占比。
例如JOIN方法,主要由Grow和WriteString组成,其中Grow很长,代表它是性能瓶颈。
net/http/pprof
net/http/pprof 提供了可视化界面来查看程序的各种指标:
- 如果本身是web应用,可以只引入pprof
- 最佳实践是为pprof准备一个专门的端口,并且该端口只可以在内网访问
trace
trace数据来源
- 从前面net/http/pprof 里面下载下来
- 使用trace包来采集
- 使用-trace的测试标记位
使用go tool trace trace.out命令来解析
常用数据:
- View trace:查看trace
- Goroutine analysis:goroutine分析
- Network blocking profile:网络阻塞
- Synchronization blocking profile:同步阻塞
- Syscall blocking profile:系统调用阻塞
- Scheduler latency profile:调度延迟
view trace
大多时候用不到
- timeline:时间线,表达执行时间
- Heap:内存占用,主要分析 goroutine哪个消耗比较大,可以用来辅助分析内存逃逸
- Goroutines:执行中的goroutine和可执行的goroutine
- Threads:系统线程
- PROCS:Go 概念中的 processor
view trace thread :可以理解为采样的时候,处于不同状态的线程的数量。
view trace processor:
- Title:名字
- Start:开始时间点
- Wall Duration:持续时间Stack Trace:开始时刻或者结束时刻的栈
- Events:发生了什么
- 可以用来分析 goroutine调度,例如从哪个goroutine切换到哪个gotouine
Go内存逃逸分析
在Go里面,对象可以被分配到栈或者堆上。分配到堆上的,被称为内存逃逸。可能的原因(不是必然引起逃逸,而是可能逃逸。是否逃逸还跟执行上下文有关):
- 指针逃逸:如方法返回局部变量指针
- interface{}逃逸:如使用interface{}作为参数或者返回值
- 接口逃逸:如以接口作为返回值
- 大对象:大对象会直接分配到堆上
- 栈空间不足
- 闭包:闭包内部引用了外部变量
- channel 传递指针
一般可以使用gcflags=-m来分析内存逃逸
指针逃逸:如方法返回局部变量指针
type User struct {
Name string
}
// 假如*User换成结构体不会逃逸,因为返回的是一个副本
func ReturnPointer() *User {
return &User{
Name: "Tom",
}
}
interface{}逃逸:如使用interface{}作为参数或者返回值
// fmt.Println() 的参数类型是any(interface{})
func Printf() {
str := "Hello"
fmt.Println(str)
}
接口逃逸:如以接口作为返回值
type Animal interface {
say()
}
type Cat struct{}
func (c Cat) say() {
fmt.Println("喵喵喵")
}
func ReturnUserV1() Cat {
c := Cat{}
return c
}
func ReturnUserV2() Animal {
c := Cat{}
return c
}
大对象:大对象会直接分配到堆上
栈空间不足
闭包:闭包内部引用了外部变量
func CountFn() func() int {
n := 0
return func() int {
n++
return n
}
}
channel 传递指针
func Channel() chan *User{
ch := make(chan *User, 1)
a := &User{
Name: "Tom",
}
ch <- a
return ch
}
每个人都有潜在的能量,只是很容易被习惯所掩盖,被时间所迷离,被惰性所消磨~