Go语言基准测试(benchmark)三部曲之三:提高篇
1.Java程序员的MacBookPro(14寸M1)配置备忘录2.群晖DS218+部署PostgreSQL(docker)3.Codespaces个性化后台服务器配置指南4.桌面版vscode用免费的微软4核8G服务器做远程开发(编译运行都在云上,还能自由创建docker服务)5.20天等待,申请终于通过,安装和体验IntelliJ IDEA新UI预览版6.LeetCode46全排列(回溯入门)7.LeetCode952三部曲之一:解题思路和初级解法(137ms,超39%)8.win11安装ubuntu(by wsl2)9.精选版:用Java扩展Nginx(nginx-clojure 入门)10.LeetCode买卖股票之一:基本套路(122)11.LeetCode297:hard级别中最简单的存在,java版,用时击败98%,内存击败百分之九十九12.支持JDK19虚拟线程的web框架,之一:体验13.支持JDK19虚拟线程的web框架,之三:观察运行中的虚拟线程14.支持JDK19虚拟线程的web框架,之四:看源码,了解quarkus如何支持虚拟线程15.支持JDK19虚拟线程的web框架,之五(终篇):兴风作浪的ThreadLocal16.快速搭建云原生开发环境(k8s+pv+prometheus+grafana)17.strimzi实战之一:简介和准备18.云端golang开发,无需本地配置,能上网就能开发和运行19.Go语言基准测试(benchmark)三部曲之一:基础篇20.Go语言基准测试(benchmark)三部曲之二:内存篇
21.Go语言基准测试(benchmark)三部曲之三:提高篇
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
-《Go语言基准测试(benchmark)三部曲》已近尾声,经历了《基础篇》和《内存篇》的实战演练,相信您已熟练掌握了基准测试的常规操作以及各种参数的用法,现在可以学习一些进阶版的技能了,在面对复杂一些的场景也能高效完成基准测试,另外还有几个坑也要提前了解,避免以后掉进去
ResetTimer
- 有时候,在基准测试前会有些准备工作,这些准备工作的耗时会影响基准测试的结果,举例如下,BenchmarkFib是常规的基准测试,而BenchmarkFibWithPrepare多了八百毫秒的准备时间
func BenchmarkFib(b *testing.B) {
for n := 0; n < b.N; n++ {
fib(30)
}
}
// BenchmarkFibWithPrepare 进入正式测试前需要耗时做准备工作的case
func BenchmarkFibWithPrepare(b *testing.B) {
// 假设这里有个耗时800毫秒的初始化操作
<-time.After(800 * time.Millisecond)
// 这下面才是咱们真正想做基准测试的代码
for n := 0; n < b.N; n++ {
fib(30)
}
}
- 同时执行上述两个基准测试,命令和结果如下,可见因为准备工作的耗时,BenchmarkFibWithPrepare方法的测试结果远不及BenchmarkFib,这与事实是不符合的,因为BenchmarkFibWithPrepare方法的测试目标没有变化,但是因为自身的准备工作导致测试结果出现较大偏差
go test -bench='BenchmarkFib|BenchmarkFibWithPrepare' benchmark-demo
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8 325 3637442 ns/op
BenchmarkFibWithPrepare-8 50 20173566 ns/op
PASS
ok benchmark-demo 14.871s
- 解决上述问题的思路是不要将准备工作的耗时算入基准测试,实现起来很简简,如下图黄色箭头所示,b.ResetTimer()重置了计时器,前面的耗时都与基准测试无关
- 再做一次基准测试,结果如下,可见800毫秒带来的偏差已被去除
go test -bench='BenchmarkFib|BenchmarkFibWithPrepare' benchmark-demo
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8 325 3616239 ns/op
BenchmarkFibWithPrepare-8 316 3729323 ns/op
PASS
ok benchmark-demo 5.628s
StopTimer & StartTimer
- 前面通过ResetTimer消除了基准测试前的多余耗时,但是如果多余的耗时出现在基准测试过程中呢?代码如下所示,fib是本次测试的目标,如果每次fib结束后都要做一些耗时的清理工作(这里用10毫秒延时来模仿),才能再次fib,那又该如何消除这10毫秒对基准测试的影响呢?
func BenchmarkFibWithClean(b *testing.B) {
// 这下面才是咱们真正想做基准测试的代码
for n := 0; n < b.N; n++ {
fib(30)
// 假设这里有个耗时100毫秒的清理操作
<-time.After(10 * time.Millisecond)
}
}
- 先来看看每次fib之后的10毫秒是否会影响基准测试,执行测试的命令和测试结果如下,可见,和没有任何耗时的BenchmarkFib方法相比,BenchmarkFibWithClean的测试结果与fib的真实性能相去甚远
go test -bench='BenchmarkFib$|BenchmarkFibWithClean' benchmark-demo
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8 322 3610100 ns/op
BenchmarkFibWithClean-8 81 16139196 ns/op
PASS
ok benchmark-demo 3.002s
- 对于这种每次调用fib之前或者之后都会出现的额外耗时操作,可以用b.StartTimer()和b.StopTimer()的组合来消除掉,简单的说就是StartTimer会开启基准测试的计时,StopTimer会暂停计时,具体的使用方法如下
// BenchmarkFibWithClean 假设每次执行完fib方法后,都要做一次清理操作
func BenchmarkFibWithClean(b *testing.B) {
// 这下面才是咱们真正想做基准测试的代码
for n := 0; n < b.N; n++ {
// 继续记录耗时
b.StartTimer()
fib(30)
// 停止记录耗时
b.StopTimer()
// 假设这里有个耗时100毫秒的清理操作
<-time.After(10 * time.Millisecond)
}
}
- 再次测试,结果如下,去除了多余耗时的基准测试结果,从之前16139196ns恢复到7448678ns,然而,和原始的没有任何处理的BenchmarkFib结果相比依然有一倍左右的差距,看来StartTimer和StopTimer本身也会带来耗时,而且在纳秒级别的测试中会显得非常明显
go test -bench='BenchmarkFib$|BenchmarkFibWithClean' benchmark-demo
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8 325 3631020 ns/op
BenchmarkFibWithClean-8 241 7448678 ns/op
PASS
ok benchmark-demo 7.751s
危险用法,提前避开
- 现在咱们对benchmark的了解已经比较全面了,可以覆盖大多数单元测试场景,下面有两个反面教材,希望咱们将来都能提前避免类似错误
- 这两个反面教材比较类似:对b.N的错误使用
- 第一个错误用法如下所示,在执行b.N次循环的时候,将当前是第几次作为入参传入了被测试的方法fib
// BenchmarkFibWrongA 演示了错误的基准测试代码,这样的测试可能无法结束
func BenchmarkFibWrongA(b *testing.B) {
for n := 0; n < b.N; n++ {
fib(n)
}
}
- 上述代码在基准测试的时候可能永远不会结束,这是因为b.N的值并不固定,可能超出了fib方法的设计范围,这样就导致出现意料之外的结果(本意是性能测试,fib的入参应该是设计范围内的),实际运行效果如下,红色箭头指向的状态一直在等待中,只能强行关闭了
- 第二种反面教材也类似,不过更简单,直接拿b.N作为入参,只调用一次fib方法,代码如下所示
func BenchmarkFibWrongB(b *testing.B) {
fib(b.N)
}
- 和前面的BenchmarkFibWrongA比,fib的执行次数似乎少了,但是请注意:b.N到底是多少呢?是否在fib方法的设计范围内?依旧没有明确答案,因此,代码也有可能永远不会结束
- 以本例中的fib为例,实际功能是斐波那契数列,我这边入参等于50的时候,fib方法的耗时是54秒,所以,如果b.N的值再大一些,例如等于100的时候,fib方法就要计算很久了,而计算较大值并不是我们做基准测试的意图
- 至此,Go语言基准测试(benchmark)三部曲就全部完成了,相信此刻的您对除了信心满满,还有就是迫不及待的想去写上一段benchmark代码,看看自己的方法函数究竟性能如何吧
- 希望这三篇文章能给您带来一些参考,golang学习路上,欣宸一路相伴
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2020-11-03 K8S环境快速部署Kafka(K8S外部可访问)
2019-11-03 docker-compose下的java应用启动顺序两部曲之二:实战