Go语言基准测试(benchmark)三部曲之二:内存篇

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):github.com/zq2599/blog...

本篇概览

  • 本文是《Go语言基准测试(benchmark)三部曲》的第二篇,目标是掌握如何用基准测试来观察被测方法的内存分配情况
  • 今天除了常规的操作,即指定参数增加内存相关的测试结果,咱们还要针对内存分配问题增加几个方法用于对比验证,最终达到根据基准测试发现内存问题的目标

基本操作

  • 查看方法中的内存使用情况,请在原来的benchmark测试命令中增加-benchmem参数,完整命令如下,用的是前文的BenchmarkFib和BenchmarkParallelFib方法做基准测试
shell 复制代码
go test -bench='Fib$' -benchmem .
  • 测试结果如下,竟然没有使用内存,不过想想也是,fib方法主要是斐波那契数列计算,未涉及到内存分配,看来这个例子不具有说明性,咱们需要写两个涉及到内存分配的方法,再对他们做基准测试看看效果

新增两个方法用于基准测试

  • 为了展现内存分配的不同程度影响,这里会编写两个方法用于对比测试
  • 这两个方法的功能是一样的:产生N个随机数(N是方法的入参),然后放入切片中
  • 虽然功能一样,但是这两个方法最大的不同就是:名为newSlice的方法,创建切片的时候没有指定切片容量,另一个名为newSliceWithCap的方法在创建切片的时候指定了切片容量
  • newSlice和newSliceWithCap方法的源码如下,都在main.go中
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文件中增加基准测试的代码,先准备三个常量,后面会用到
go 复制代码
const (
	SLICE_LENGTH_MILLION         = 1000000   // 往切片中添加数据的长度,百万
	SLICE_LENGTH_TEN_MILLION     = 10000000  // 往切片中添加数据的长度,千万
	SLICE_LENGTH_HUNDRED_MILLION = 100000000 // 往切片中添加数据的长度,亿
)
  • 然后是两个基准测试的方法,分别用于测试newSlice和newSliceWithCap:
go 复制代码
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这两个方法
shell 复制代码
go test -bench='BenchmarkNewSlice$|BenchmarkNewSliceWithCap$' -benchmem .
  • 结果如下,可见未指定容量的切片在保存数据时会触发扩容,会分配更多内存,内存分配次数也会跟多,每次方法执行的耗时也更多,而提前指定了容量的切片,中途不再发生扩容,内存分配量更小,方法执行耗时也更少(对我们的开发还是有指导意义的)
shell 复制代码
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中增加一个方法
go 复制代码
func testNewSlice(len int, b *testing.B) {
	for n := 0; n < b.N; n++ {
		newSlice(len)
	}
}
  • 现在只要新增多个BenchmarkXXX方法,每个方法都调用testNewSlice并传入不同数量级的数字,就能实现对比测试了,详细代码如下,咱们分解测试百万、千万、亿这三个级别的数据量下newSlice的内存分配情况
go 复制代码
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)
}
  • 执行以下命令测试,只会匹配到上面新增的三个测试方法
shell 复制代码
go test -bench='Million$' -benchmem .
  • 同一方法,处理不同数量级内容的对比测试结果如下,可见不指定容量的切片存入数据时,数据量越大,对性能的影响越严重
shell 复制代码
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的基本功能已经掌握,接下来的《提高篇》会有更多进阶内容,协助咱们完成更加全面精确的基准测试,敬请期待,欣宸原创,必不让您失望

欢迎关注掘金:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

相关推荐
IT_陈寒2 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码3 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo3 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志4 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..4 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects4 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰4 小时前
一些基础和问题解决
后端
陈随易4 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN5 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana5 小时前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端