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

输出:
image.png

内存和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很长,代表它是性能瓶颈。
image.png

net/http/pprof

image.png
net/http/pprof 提供了可视化界面来查看程序的各种指标:

  • 如果本身是web应用,可以只引入pprof
  • 最佳实践是为pprof准备一个专门的端口,并且该端口只可以在内网访问

trace

image.png
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
大多时候用不到
image.png

  • timeline:时间线,表达执行时间
  • Heap:内存占用,主要分析 goroutine哪个消耗比较大,可以用来辅助分析内存逃逸
  • Goroutines:执行中的goroutine和可执行的goroutine
  • Threads:系统线程
  • PROCS:Go 概念中的 processor

view trace thread :可以理解为采样的时候,处于不同状态的线程的数量。
image.png
view trace processor:
image.png

  • 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
}
posted @ 2022-12-25 02:01  请务必优秀  阅读(176)  评论(0编辑  收藏  举报