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)三部曲》的第二篇,目标是掌握如何用基准测试来观察被测方法的内存分配情况
- 今天除了常规的操作,即指定参数增加内存相关的测试结果,咱们还要针对内存分配问题增加几个方法用于对比验证,最终达到根据基准测试发现内存问题的目标
基本操作
- 查看方法中的内存使用情况,请在原来的benchmark测试命令中增加-benchmem参数,完整命令如下,用的是前文的BenchmarkFib和BenchmarkParallelFib方法做基准测试
go test -bench='Fib$' -benchmem .
- 测试结果如下,竟然没有使用内存,不过想想也是,fib方法主要是斐波那契数列计算,未涉及到内存分配,看来这个例子不具有说明性,咱们需要写两个涉及到内存分配的方法,再对他们做基准测试看看效果
新增两个方法用于基准测试
- 为了展现内存分配的不同程度影响,这里会编写两个方法用于对比测试
- 这两个方法的功能是一样的:产生N个随机数(N是方法的入参),然后放入切片中
- 虽然功能一样,但是这两个方法最大的不同就是:名为newSlice的方法,创建切片的时候没有指定切片容量,另一个名为newSliceWithCap的方法在创建切片的时候指定了切片容量
- newSlice和newSliceWithCap方法的源码如下,都在main.go中
// 往切片中放入指定数量的随机数,这个切片没有提前设置容量
func newSlice(n int) []int {
rand.Seed(time.Now().UnixNano())
// 注意,这里在生成切片的时候并没有指定容量
nums := make([]int, 0)
for i := 0; i < n; i++ {
nums = append(nums, rand.Int())
}
return nums
}
// 往切片中放入指定数量的随机数,这个切片提前设置了容量
func newSliceWithCap(n int) []int {
rand.Seed(time.Now().UnixNano())
// 注意,这里在生成切片的时候指定了容量
nums := make([]int, 0, n)
for i := 0; i < n; i++ {
nums = append(nums, rand.Int())
}
return nums
}
- 接下来在main_test.go文件中增加基准测试的代码,先准备三个常量,后面会用到
const (
SLICE_LENGTH_MILLION = 1000000 // 往切片中添加数据的长度,百万
SLICE_LENGTH_TEN_MILLION = 10000000 // 往切片中添加数据的长度,千万
SLICE_LENGTH_HUNDRED_MILLION = 100000000 // 往切片中添加数据的长度,亿
)
- 然后是两个基准测试的方法,分别用于测试newSlice和newSliceWithCap:
func BenchmarkNewSlice(b *testing.B) {
for n := 0; n < b.N; n++ {
newSlice(SLICE_LENGTH_MILLION)
}
}
func BenchmarkNewSliceWithCap(b *testing.B) {
for n := 0; n < b.N; n++ {
newSliceWithCap(SLICE_LENGTH_MILLION)
}
}
- 代码写完了,从理论上分析,切片未指定容量,就会随着内容的增加发生新的内存分配,因此newSlice的内存使用和内存分配都应该超过newSliceWithCap,咱们来测试一下,看数据和推论是否匹配
- 执行以下命令,正则表达式的意思是只执行BenchmarkNewSlice和BenchmarkNewSliceWithCap这两个方法
go test -bench='BenchmarkNewSlice$|BenchmarkNewSliceWithCap$' -benchmem .
- 结果如下,可见未指定容量的切片在保存数据时会触发扩容,会分配更多内存,内存分配次数也会跟多,每次方法执行的耗时也更多,而提前指定了容量的切片,中途不再发生扩容,内存分配量更小,方法执行耗时也更少(对我们的开发还是有指导意义的)
go test -bench='BenchmarkNewSlice$|BenchmarkNewSliceWithCap$' -benchmem .
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkNewSlice-8 68 16568869 ns/op 41678153 B/op 38 allocs/op
BenchmarkNewSliceWithCap-8 84 14098503 ns/op 8003589 B/op 1 allocs/op
PASS
ok benchmark-demo 2.769s
同一方法的不同数量级对比
- 经过前面的测试,可以确定newSliceWithCap方法由于未指定切片容量,在保存数据的中途会触发扩容,从而导致内存分配的大小和次数都会增加
- 这个结果是对比newSlice方法得出的,此方法指定了切片容量的,接下里咱们换种测试方式:让newSliceWithCap内的切片分别存入不同数量级的数据,观察此方法在面对这些数据时的内存分配情况
- 在main_test.go中增加一个方法
func testNewSlice(len int, b *testing.B) {
for n := 0; n < b.N; n++ {
newSlice(len)
}
}
- 现在只要新增多个BenchmarkXXX方法,每个方法都调用testNewSlice并传入不同数量级的数字,就能实现对比测试了,详细代码如下,咱们分解测试百万、千万、亿这三个级别的数据量下newSlice的内存分配情况
func BenchmarkNewSlicMillion(b *testing.B) {
testNewSlice(SLICE_LENGTH_MILLION, b)
}
func BenchmarkNewSlicTenMillion(b *testing.B) {
testNewSlice(SLICE_LENGTH_TEN_MILLION, b)
}
func BenchmarkNewSlicHundredMillion(b *testing.B) {
testNewSlice(SLICE_LENGTH_HUNDRED_MILLION, b)
}
- 执行以下命令测试,只会匹配到上面新增的三个测试方法
go test -bench='Million$' -benchmem .
- 同一方法,处理不同数量级内容的对比测试结果如下,可见不指定容量的切片存入数据时,数据量越大,对性能的影响越严重
go test -bench='Million$' -benchmem .
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkNewSlicMillion-8 67 16283754 ns/op 41678145 B/op 38 allocs/op
BenchmarkNewSlicTenMillion-8 7 159938941 ns/op 492000525 B/op 49 allocs/op
BenchmarkNewSlicHundredMillion-8 1 2242365417 ns/op 4589008224 B/op 60 allocs/op
- 至此,基准测试的内存篇就完成了,相信大家对benchmark的基本功能已经掌握,接下来的《提高篇》会有更多进阶内容,协助咱们完成更加全面精确的基准测试,敬请期待,欣宸原创,必不让您失望
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2021-11-02 spring-cloud-square源码速读(spring-cloud-square-okhttp篇)
2020-11-02 K8S的StorageClass实战(NFS)
2019-11-02 docker-compose下的java应用启动顺序两部曲之一:问题分析