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
相关推荐
RedJACK~5 小时前
Go Ebiten小游戏开发:扫雷
开发语言·后端·golang
研究司马懿6 小时前
【ETCD】ETCD——confd配置管理
数据库·golang·自动化·运维开发·etcd·argocd·gitops
aloha_78912 小时前
测试开发工程师面经准备(sxf)
java·python·leetcode·压力测试
程序员杰哥14 小时前
软件测试之压力测试详解
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·压力测试
卓码软件测评15 小时前
软件数据库测试:【数据库质量保障:从单元测试到性能优化】
运维·数据库·测试用例·压力测试
QT 小鲜肉1 天前
【个人成长笔记】在 Linux 系统下撰写老化测试脚本以实现自动压测效果(亲测有效)
linux·开发语言·笔记·单片机·压力测试
钢门狂鸭1 天前
go开发规范指引
开发语言·驱动开发·golang
脚踏实地的大梦想家1 天前
【Go】P19 Go语言并发编程核心(三):从 Channel 安全到互斥锁
开发语言·安全·golang
Tony Bai1 天前
Go GUI 开发的“绝境”与“破局”:2025 年现状与展望
开发语言·后端·golang
豆浆whisky1 天前
Go分布式追踪实战:从理论到OpenTelemetry集成|Go语言进阶(15)
开发语言·分布式·golang