go资深之路笔记(八) 基准测试

一、用法

aa_test.go

bash 复制代码
func BenchmarkAdd(b *testing.B) {
	a := "shdhAAsaBBjhsd"
	for i := 0; i < b.N; i++ {
		res := strings.Replace(a, "AA", "BB", -1)
		_ = res

	}
}

然后执行测试:

bash 复制代码
go test --bench=BenchmarkAdd --benchmem aa_test.go

输出:

细节分析:

  1. 脚本名必须是 xxx_test.go
  2. 函数名必须是 Benchmark开头
  3. 函数参数必须是 *testing.B
  4. _ = res 是为了防止go编译器进行优化(当然去掉编译也不会通过)
  5. --bench 是指定函数,他的值是正则表达式。 --bench=. 是匹配所有基准函数
  6. --benchmem 是分析内存分配
  7. 参数前的两个横杠,也可以用一个。 --bench等于-bench【不得不说go的设计真的很人性】
  8. 图片红框第一个 执行次数,剩下三个是每次执行时消耗时间,分配内存字节,分配内存次数

二、参数大全

bash 复制代码
# 启用内存分配统计
go test -bench=. -benchmem

### 性能分析 ##################
# 指定测试运行时间
go test -bench=. -benchtime=5s      # 运行5秒
go test -bench=. -benchtime=100x    # 运行100次迭代	b.N = 100

# 一键执行多次
go test -bench=. -count=10

# 并行运行测试(cpu多不一定变快,除非有多goroutine任务)
## 这里要说一下有意思的点,开一个goroutine的 2-4KB 栈内存以及一定时间,如果对应耗时比较少的任务,
##其实根本没必要开 goroutine,因为你开goroutine有可能耗时更多,内存更多是绝对的,所以goroutine不是万金油。
go test -bench=. -cpu=4

### cpu 内存分析 借助 pprof ################## 
# 生成 CPU profile
go test -bench=. -cpuprofile=cpu.pprof

# 生成内存 profile  
go test -bench=. -benchmem -memprofile=mem.pprof

# 生成阻塞 profile
go test -bench=. -blockprofile=block.pprof

# 生成互斥锁 profile
go test -bench=. -mutexprofile=mutex.pprof


#### 并发和调度相关 #################
# 指定不同的 GOMAXPROCS 值
go test -bench=. -cpu=1,2,4,8

# 设置并行度(与 -cpu 不同)
go test -bench=. -parallel=8

# 跟踪调度信息(调试用)
go test -bench=. -trace=trace.out

2.1 b.RunParallel

看了上面的参数,你大概知道想要实现并发效果,并不是加go就行的,这里可以用
b.RunParallel(这个函数的内部实现大概是启用goroutine的数量等同于GOMAXPROCS(go可用核心数),或者GOMAXPROCS的倍数;从而实现cpu核心的充分使用。)

bash 复制代码
func cal(a string) {
	res := strings.Replace(a, "AA", "BB", -1)
	_ = res
}

// var res = ""
var chan1 = make(chan struct{}, 8)

func BenchmarkAdd(b *testing.B) {
	a := "shdhAAsaBBjhsd"
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			cal(a)
		}
	})
}

执行:

bash 复制代码
go test -bench=BenchmarkAdd -benchmem -cpu=1,2,4,8,16,24 aa_test.go

效果:

2.2 有缓存的channel

有个很有意思的点:现实环境下,不可能让goroutine一直上涨,特别是cpu密集型任务,不限制的话会内存和cpu会爆炸性增长!!(for go func() 就会如此, b.RunParallel 不会是因为限制了goroutin数量)但是一般普通的限制方式,比如 用有缓存的channel来限制,你会发现cpu不是越大越好:

bash 复制代码
func cal(a string) {
	defer func() {
		<-chan1
	}()
	res := strings.Replace(a, "AA", "BB", -1)
	_ = res
}

var chan1 = make(chan struct{}, 8)

func BenchmarkAdd(b *testing.B) {
	a := "shdhAAsaBBjhsd"
	//for i := 0; i < b.N; i++ {	
	//	go cal(a)
	//}
	b.SetParallelism(10)
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			chan1 <- struct{}{}		// 用channel来控制同时并发goroutine数量
			cal(a)
		}
	})
}

执行你会发现cpu越大你会:

原因是: 有缓存的channel 内部构造是 有锁的,也就是说 存在多个cpu争抢同一个内存的情况,这个时候可能会涉及缓存一致性,以及获取锁过程中阻塞或者自旋而产生的消耗。

这就产生了两个问题:

  1. channel限流的操作并不一定适合,可以优化;【这个之后的文章会再写,篇幅不够】
  2. 并不是cpu数量越大约好的。(这个其实再上一个例子的BenchmarkAdd-24 也能看出来)

2.3 总结:

这是探索命令过程中的一些笔记,可能有点乱,稍微总结下

  1. b.RunParallel 函数可以用来测试代码在那个核心数下运行最佳(也可以测是否panic)
  2. 用有缓存channel 来限流会导致争抢,cpu核心数越高越明显(可优化)
  3. 基准测试的时候其实是没有计算goroutine创建的消耗和内存的,实际上一个goroutine创建占用内存 是2kB~4kB, 所以除了io密集型或者cpu密集型任务,有时候可能用同步的方式执行会更快

2.4 和 pprof联合工作

就是上面的命令

bash 复制代码
# 生成 CPU profile
go test -bench=. -cpuprofile=cpu.pprof

# 生成内存 profile  
go test -bench=. -benchmem -memprofile=mem.pprof

# 生成阻塞 profile
go test -bench=. -blockprofile=block.pprof

# 生成互斥锁 profile
go test -bench=. -mutexprofile=mutex.pprof

需要先安装 graphviz(火焰图生成工具)

bash 复制代码
sudo apt-get install graphviz

直接用 pprof工具打开再网页展示

bash 复制代码
 go tool pprof -http=localhost:8080  cpu.out

2.5 和 trace联合工作

生成 trace测试文件

bash 复制代码
go test -bench=. -benchtime=10000x -trace=trace.out

用trace工具打开文件

bash 复制代码
go tool trace trace.out

ps:-benchtime=10000x 代表执行10000次,因为不指定的话一个测试动则几十上百万次,trace根本打不开

三、坑点:

1.循环开始前有耗时也会被计入,我们可以用 b.ResetTimer() 重置计时器,排除上方初始化时间

或者用 b.StopTimer() 和 b.StartTimer() 开始计时

  1. 如果在 b.Loop() 或 b.N 循环中使用了 break 等语句提前退出,会导致实际迭代次数远少于预期,测试结果严重失准,且框架可能不会告警。

  2. 善用 benchtime 和count: 测量非常快(纳秒级)的操作时,单次测试结果极易受机器负载、电源管理等环境因素干扰,导致结果不稳定。

    使用 -benchtime 延长单次测试时间,例如 -benchtime=5s。

    使用 -count 多次运行测试,并结合 benchstat 工具进行统计分析
    benchstat 用法:

bash 复制代码
go test -bench=BenchmarkAdd -benchmem -count=5 aa_test.go | tee stats.txt
benchstat stats.txt
相关推荐
星星点点洲1 天前
PostgreSQL 15二进制文件
开发语言·设计模式·golang
youliroam1 天前
成语接龙学习
学习·golang·uniapp·成语接龙
驰羽1 天前
[GO]什么是热重载,如何使用Air工具
开发语言·后端·golang
BUG弄潮儿1 天前
go-swagger标准接口暴露
开发语言·后端·golang
qq_339191141 天前
go win安装grpc-gen-go插件
开发语言·后端·golang
云闲不收2 天前
golang的一些技巧
开发语言·后端·golang
Achou.Wang2 天前
源码分析 golang bigcache 高性能无 GC 开销的缓存设计实现
开发语言·缓存·golang
Yeats_Liao2 天前
Go语言技术与应用(二):分布式架构设计解析
开发语言·分布式·golang
蓝婴天使2 天前
基于 React + Go + PostgreSQL + Redis 的管理系统开发框架
react.js·postgresql·golang