首页 > 系统相关 >Go语言基准测试(benchmark)三部曲之二:内存篇

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

时间:2023-11-02 09:23:10浏览次数:35  
标签:newSlice benchmark 切片 内存 测试 Go 方法 op

欢迎访问我的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 // 往切片中添加数据的长度,亿
)
  • 然后是两个基准测试的方法,分别用于测试newSlicenewSliceWithCap
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,咱们来测试一下,看数据和推论是否匹配
  • 执行以下命令,正则表达式的意思是只执行BenchmarkNewSliceBenchmarkNewSliceWithCap这两个方法
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的基本功能已经掌握,接下来的《提高篇》会有更多进阶内容,协助咱们完成更加全面精确的基准测试,敬请期待,欣宸原创,必不让您失望

欢迎关注博客园:程序员欣宸

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

标签:newSlice,benchmark,切片,内存,测试,Go,方法,op
From: https://www.cnblogs.com/bolingcavalry/p/17724778.html

相关文章

  • go中的内存逃逸
    内存逃逸(memoryescape)是指在编写Go代码时,某些变量或数据的生命周期超出了其原始作用域的情况。当变量逃逸到函数外部或持续存在于堆上时,会导致内存分配的开销,从而对程序的性能产生负面影响。Go编译器会进行逃逸分析,以确定哪些变量需要在堆上分配内存。下面将详细分析Go语言......
  • ⭐ go gorm 映射框架 好用到爆炸!!!
    使用Golandide插件搜索Gorm直接安装连接数据库并且选择表,鼠标右键gorm之后按照你的项目要求生成crud直接快人一步释放双手啦......
  • Linux内存管理的分页机制
    分段机制的原理如下:分段机制下的虚拟地址由两部分组成,段选择子和段内偏移量。段选择子就保存在段寄存器里面。段选择子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等。虚拟地址中的段内偏移量应该位于0和段界限之间。如果段内偏移量......
  • sizeof与各数据结构内存占用计算
    一、sizeof1.sizeof介绍sizeof会计算参数的数据类型所占字节数。注意事项:如果是数组类型(非vector),则会返回整个数组所占字节数。sizeof是运算符,在编译期间确定,因此无法计算动态分配的内存大小,如new等。2.实现方式获取type使用getTypeInfoChars(type)来计算字节......
  • Godot C# 可能遇到的问题和解决办法
    一、没有添加C#脚本的选项,即只能使用GDScript。如图原因:安装的godot版本不对解决办法:到官网安装.net版本(https://godotengine.org/download/windows/)不是蓝框而是灰色框的GodotEngine-.NET版  二、成功创建了C#脚本,但是......
  • django搭建平台实战教程二:快速实现用户注册和登录
    这一篇主要使用django框架实现用户注册和登录编写接口并设置URL根目录添加api文件夹,views.py添加register注册视图@api_view(['POST'])defregister(request:Request):ifDUser.objects.filter(username=request.data["username"]).count()>0:returnRespon......
  • 2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金, 现在有一位小
    2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金,现在有一位小偷计划从这些房屋中窃取现金,由于相邻的房屋装有相互连通的防盗系统,所以小偷不会窃取相邻的房屋,小偷的窃取能力定义为他在窃取过程中能从单间房屋中窃取的最大金额,给你一个整数数组nums......
  • 2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金, 现在有一位小
    2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金,现在有一位小偷计划从这些房屋中窃取现金,由于相邻的房屋装有相互连通的防盗系统,所以小偷不会窃取相邻的房屋,小偷的窃取能力定义为他在窃取过程中能从单间房屋中窃取的最大金额,给你一个整数数组nums表示每......
  • django搭建平台实战教程一:生成数据库数据
    首先需要创建一个django-rest-framework项目,如何创建可以参考https://www.django-rest-framework.org/tutorial/quickstart/,不再赘述。创建完结构如图所示 settings.py配置mysql数据库...DATABASES={"default":{"ENGINE":"django.db.backends.mysql",......
  • 通过GO的全名描述获得GO ID
    #loadtheGOlibrarylibrary(GO.db)#extractanamedvectorofalltermsgoterms<-Term(GOTERM)#workwithitinR,orexportittoafilewrite.table(goterms,sep="\t",file="goterms.txt")#全部信息保存为文件REFhttps://www.biost......