Go-知识测试-性能测试

Go-知识测试-性能测试

  • 1. 定义
  • 2. 例子
  • 3. testing.common 测试基础数据
  • 4. testing.TB 接口
  • 5. 关键函数
    • 5.1 testing.runBenchmarks
    • 5.2 testing.B.runN
    • 5.3 testing.B.StartTimer
    • 5.4 testing.B.StopTimer
    • 5.5 testing.B.ResetTimer
    • 5.6 testing.B.Run
    • 5.7 testing.B.run1
    • 5.8 testing.B.run
    • 5.9 processBench
    • 5.10 tetsing.B.doBench
    • 5.11 testing.B.launch
    • 5.12 testing.B.SetBytes
  • 6. 数据统计

建议先看:https://blog.csdn.net/a18792721831/article/details/140062769

Go-知识测试-工作机制

1. 定义

性能测试会执行多次,然后计算平均耗时。
性能测试要保证测试文件以_test.go结尾。
测试方法必须以BenchmarkXxx开头。
测试文件可以与源码处于同一目录,也可以处于单独的目录。

2. 例子

在创建切片的时候,可以指定容量,也可以不指定容量。假设可以提前知道数据的长度,就可以在创建切片的时候,预分配存储空间,避免多次拷贝。
函数如下:

func MakeWithout(n int) []int {
	var s []int
	for i := 0; i < n; i++ {
		s = append(s, i)
	}
	return s
}

func MakeWith(n int) []int {
	s := make([]int, n)
	for i := 0; i < n; i++ {
		s = append(s, i)
	}
	return s
}

接着使用性能测试,看看上面两个函数的性能差距有多大

func BenchmarkMakeWithout(b *testing.B) {
	for i := 0; i < b.N; i++ {
		MakeWithout(1000)
	}
}

func BenchmarkMakeWith(b *testing.B) {
	for i := 0; i < b.N; i++ {
		MakeWith(1000)
	}
}

先来个小容量的,n=1000
使用go test -v -bench=.执行性能测试,-v 表示控制台输出结果,-bench表示执行性能测试,-bench=.表示使用.作为正则,也就是执行全部的性能测试
在这里插入图片描述

通过输出可以知道 Without 执行了 403447 次,平均每次 2596 纳秒
With 执行了 329895次,平均每次 3357 纳秒
也就是说,在1000的容量下,预先分配反而慢。我猜测是显式调用make花费了时间。
加大容量,n=1000_0000
在这里插入图片描述

预分配比较快了,平均每次26.7毫秒

3. testing.common 测试基础数据

每个性能测试都有一个入参t *testing.B,结构定义如下:

type B struct {
	common // 与 testing.T 共享的 testing.common ,负责记录日志、状态等
	importPath       string // 包含基准的包的导入路径
	context          *benchContext
	N                int // 目标代码执行次数,不需要用户了解具体值,会自动调整
	previousN        int           // 上一次运行中的迭代次数
	previousDuration time.Duration // 上次运行的总持续时间
	benchFunc        func(b *B) // 性能测试函数
	benchTime        benchTimeFlag  // 性能测试函数最少执行的时间,默认为1s
	bytes            int64 // 每次迭代处理的字节数
	missingBytes     bool // 其中一个子基准标记没有设置字节。
	timerOn          bool // 是否已开始计时 
	showAllocResult  bool
	result           BenchmarkResult // 测试结果
	parallelism      int // RunParallel创建并行性*GOMAXPROCS goroutines
	// memStats的初始状态。Mallocs和MemStats。TotalAlloc。
	startAllocs uint64 // 计时开始时堆中分配的对象总数
	startBytes  uint64 // 计时开始时堆中分配的字节总数
	// 运行后此测试的净总数。
	netAllocs uint64 // 计时结束时,堆中增加的对象总数
	netBytes  uint64 // 计时结束时,堆中增加的字节总数
	// ReportMetric收集的额外指标。
	extra map[string]float64
}

T 组合了 common 类型

// common包含T和B之间的公共元素,以及
// 捕获常见的方法,如Errorf。
type common struct {
	mu          sync.RWMutex         // 保卫这群田地
	output      []byte               // 测试或基准测试生成的输出。
	w           io.Writer            // 对于flushToParent。
	ran         bool                 // 执行了测试或基准测试(或其中一个子测试)。
	failed      bool                 // 测试或基准测试失败。
	skipped     bool                 // 已跳过测试或基准测试。
	done        bool                 // 测试已完成,所有子测试均已完成。
	helperPCs   map[uintptr]struct{} // 写入文件/行信息时要跳过的函数
	helperNames map[string]struct{}  // helperPC转换为函数名
	cleanups    []func()             // 测试结束时要调用的可选函数
	cleanupName string               // 清除函数的名称。
	cleanupPc   []uintptr            // 调用Cleanup的点处的堆栈跟踪。
	finished    bool                 // 测试功能已完成。
	chatty      *chattyPrinter       // 如果设置了chatty标志,则为chattyPrinter的副本。
	bench       bool                 // 当前测试是否为基准测试。
	hasSub      int32                // 以原子形式书写。
	raceErrors  int                  // 测试过程中检测到的种族数。
	runner      string               // 运行测试的tRunner的函数名称。
	parent      *common
	level       int       // 测试或基准的嵌套深度。
	creator     []uintptr // 如果级别>0,则堆栈跟踪父级调用t.Run的点。
	name        string    // 测试或基准的名称。
	start       time.Time // 时间测试或基准测试已启动
	duration    time.Duration
	barrier     chan bool // 为了发出平行子测验的信号,他们可以开始。
	signal      chan bool // 发出测试完成的信号。
	sub         []*T      // 要并行运行的子测试的队列。
	tempDirMu   sync.Mutex
	tempDir     string
	tempDirErr  error
	tempDirSeq  int32
}

每个测试均对应一个 testing.common,不仅记录了测试函数的基础信息(比如名字),还管理了测试的执行过程和测试结果。
testing.commong是单元测试,性能测试和模糊测试的基础。
通过继承共同的结构,保证了各种测试的行为一致,降低使用的门槛。

4. testing.TB 接口

testing.common 实现的接口为 testing.TB,单元测试和性能测试通过该接口获取基础能力。

type TB interface {
	Cleanup(func())                            // 清理
	Error(args ...interface{})                 // 表示测试失败+记录日志
	Errorf(format string, args ...interface{}) // 格式化表示测试失败+记录日志
	Fail()                                     // 表示测试失败
	FailNow()                                  // 标记测试失败+结束当前测试
	Failed() bool                              // 查询结果
	Fatal(args ...interface{})                 // 标记测试失败+记录日志+结束当前测试
	Fatalf(format string, args ...interface{}) // 格式化标记测试失败+记录日志+结束当前测试
	Helper()                                   // 标记测试为 Helper (避免打印当前代码行号)
	Log(args ...interface{})                   // 记录日志
	Logf(format string, args ...interface{})   // 格式化 记录日志
	Name() string                              // 查询测试名
	Setenv(key, value string)                  // 设置环境变量
	Skip(args ...interface{})                  // 记录日志+跳过测试
	SkipNow()                                  // 跳过测试
	Skipf(format string, args ...interface{})  // 格式化记录日志+跳过测试
	Skipped() bool                             // 查询测试是否被跳过
	TempDir() string                           // 返回一个临时目录
	//阻止用户实现的私有方法
	//接口,因此将来不会添加
	//违反Go 1兼容性。
	private()
}

5. 关键函数

5.1 testing.runBenchmarks

runBenchmarks负责创建name=Main的Benchmark作为启动case
在这里插入图片描述

在testing.B.runN中执行testing.B.Run

5.2 testing.B.runN

在runN中启动定时器,然后执行benchFunc
在这里插入图片描述

性能测试中,执行多少次,也时由runN中设置的

5.3 testing.B.StartTimer

StartTimer负责启动计时并初始化内存相关计数,测试执行时会自动调用(name=Main的testing.B启动),一般不需要用户启动

func (b *B) StartTimer() {
	if !b.timerOn {
		runtime.ReadMemStats(&memStats) // 读取当前堆内存分配信息
		b.startAllocs = memStats.Mallocs // 记录当前堆内存分配的对象数
		b.startBytes = memStats.TotalAlloc // 记录当前堆内存分配的字节数
		b.start = time.Now() // 记录测试启动时间
		b.timerOn = true // 标记计时标志
	}
}

5.4 testing.B.StopTimer

StopTimer负责停止计时,并累加相应的统计值:

func (b *B) StopTimer() {
	if b.timerOn {
		b.duration += time.Since(b.start) // 累加测试耗时
		runtime.ReadMemStats(&memStats) // 读取当前堆内存分配信息
		b.netAllocs += memStats.Mallocs - b.startAllocs // 累加对北村分配的对象数
		b.netBytes += memStats.TotalAlloc - b.startBytes // 累加堆内存分配的字节数
		b.timerOn = false // 标记计时标志
	}
}

5.5 testing.B.ResetTimer

ResetTime用于重置计时器,相应地也会把其他统计值也重置:

func (b *B) ResetTimer() {
	if b.timerOn {
		runtime.ReadMemStats(&memStats) // 读取当前堆内存分配信息
		b.startAllocs = memStats.Mallocs // 记录当前堆内存分配的对象数
		b.startBytes = memStats.TotalAlloc // 记录当前堆内存分配的字节数
		b.start = time.Now() // 记录测试启动时间
	}
	b.duration = 0 // 清空耗时
	b.netAllocs = 0 // 清空内存分配的对象数
	b.netBytes = 0 // 清空内存分配的字节数
}

ResetTimer必将常用,比如在一个测试中,初始化部分耗时比较长,初始化后再开始计时

5.6 testing.B.Run

func (b *B) Run(name string, f func(b *B)) bool {
	// 是否有子测试
	atomic.StoreInt32(&b.hasSub, 1)
	// 加锁
	benchmarkLock.Unlock()
	// 延迟解锁
	defer benchmarkLock.Lock()
	// 获取 name等信息
	benchName, ok, partial := b.name, true, false
	// name 进行匹配
	if b.context != nil {
		benchName, ok, partial = b.context.match.fullName(&b.common, name)
	}
	// 匹配失败,结束
	if !ok {
		return true
	}
	var pc [maxStackLen]uintptr
	n := runtime.Callers(2, pc[:])
	// 新建子测试数据结构
	sub := &B{
		common: common{
			signal:  make(chan bool),
			name:    benchName,
			parent:  &b.common,
			level:   b.level + 1,
			creator: pc[:n],
			w:       b.w,
			chatty:  b.chatty,
			bench:   true,
		},
		importPath: b.importPath,
		benchFunc:  f,
		benchTime:  b.benchTime,
		context:    b.context,
	}
	// 是否并发
	if partial {
		atomic.StoreInt32(&sub.hasSub, 1)
	}
	// 输出日志信息
	if b.chatty != nil {
		labelsOnce.Do(func() {
			fmt.Printf("goos: %s\n", runtime.GOOS)
			fmt.Printf("goarch: %s\n", runtime.GOARCH)
			if b.importPath != "" {
				fmt.Printf("pkg: %s\n", b.importPath)
			}
			if cpu := sysinfo.CPU.Name(); cpu != "" {
				fmt.Printf("cpu: %s\n", cpu)
			}
		})
		fmt.Println(benchName)
	}
	// 先执行一次子测试,如果子测试不出错且子测试没有子测试则继续执行run
	if sub.run1() {
	    // run 中决定了要执行多少次runN
		sub.run()
	}
	// 累加统计结果到父测试中
	b.add(sub.result)
	return !sub.failed
}

所有的测试都是先使用run1方法执行一次,然后在决定要不要继续迭代。
测试结果实际上以最后一次迭代的数据为准,最后一次迭代往往B.N更大,测试准确性相对更高。

5.7 testing.B.run1

在这里插入图片描述

在 run1中,调用runN的时候,传入1,表示执行一次BenchmarkXxx方法,统计执行一次的耗时。

5.8 testing.B.run

func (b *B) run() {
    // 打印额外的统计信息
	labelsOnce.Do(func() {
		fmt.Fprintf(b.w, "goos: %s\n", runtime.GOOS)
		fmt.Fprintf(b.w, "goarch: %s\n", runtime.GOARCH)
		if b.importPath != "" {
			fmt.Fprintf(b.w, "pkg: %s\n", b.importPath)
		}
		if cpu := sysinfo.CPU.Name(); cpu != "" {
			fmt.Fprintf(b.w, "cpu: %s\n", cpu)
		}
	})
	// 如果是子测试,那么此时子测试还未执行run1,在 processBench中会对子测试创建一个B,然后执行run1,接着执行 doBench
	if b.context != nil {
		// Running go test --test.bench
		b.context.processBench(b) // Must call doBench.
	} else {
		// Running func Benchmark.
		b.doBench()
	}
}

5.9 processBench

在这里插入图片描述

执行子测试的doBench

5.10 tetsing.B.doBench

func (b *B) doBench() BenchmarkResult {
	go b.launch() // goroutine 执行 launch 结束
	<-b.signal
	return b.result
}

5.11 testing.B.launch

func (b *B) launch() {
    // 延迟调用通知父测试结束
	defer func() {
		b.signal <- true
	}()
	// 如果用户指定了,那么按照用户指定的执行
	if b.benchTime.n > 0 {
		b.runN(b.benchTime.n)
	} else {
	    // 获取默认的时间间隔,默认为1s
		d := b.benchTime.d
		// 最少执行 b.benchTime(默认为1s)时间,最多执行1e9次
		for n := int64(1); !b.failed && b.duration < d && n < 1e9; {
			last := n
			// 获取1秒的纳秒数
			goalns := d.Nanoseconds()
			// 获取上一次执行次数,1次
			prevIters := int64(b.N)
			// 获取上一次执行时间
			// 执行 run 之前需要执行一次 run1 也就是说 prevIters 是第一次执行的耗时
			prevns := b.duration.Nanoseconds()
			if prevns <= 0 {
				// Round up, to avoid div by zero.
				prevns = 1
			}
		    // goalns * prevIters 上次执行持续了多少纳秒
		    // prevns 上次执行一次的耗时
		    // n 表示上次执行多少次 
			n = goalns * prevIters / prevns
			// 先增长 20% , n = 1.2n
			n += n / 5
			// 不能增加过快,如果 20% 比100倍还大,那么取小值
			n = min(n, 100*last)
			// 并且至少增加1次
			n = max(n, last+1)
			// 不能超过 1e9
			n = min(n, 1e9)
			// 启动执行
			b.runN(int(n))
			// 执行完成后,进行下一次循环
		}
	}
	// 统计结果
	b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes, b.extra}
}

在不考虑程序出错,而且用户没有主动停止测试的场景下,每个测试至少执行b.benchTime长的时间(秒),默认为1s.
先执行一遍,看看用户代码执行一次需要花多长时间,如果时间比较短,那么B.N需要足够大,才可以测试更准确。
如果时间比较长,那么B.N需要足够少,否则测试效率比较慢。
n = goalns * prevIters / prevns ,如果 prevns比较少,那么n就会从一个比较大的值开始循环
如果prevns比较大,那么n就会以一个比较小的值开始循环,直到单批次超过1秒。

5.12 testing.B.SetBytes

这个函数用来设置单词迭代处理的字节数,一旦设置了这个字节数,那么输出报告中奖出现 xx MB/s 的信息。
用来表示待测函数处理字节的性能,待测函数每次处理多少字节只有用户知道,所以需要用户设置。
比如:

func MakeWithout(n int) []int {
	var s []int
	for i := 0; i < n; i++ {
		s = append(s, i)
	}
	return s
}

func MakeWith(n int) []int {
	s := make([]int, n)
	for i := 0; i < n; i++ {
		s = append(s, i)
	}
	return s
}

func BenchmarkMakeWithout(b *testing.B) {
	b.SetBytes(1024)
	for i := 0; i < b.N; i++ {
		MakeWithout(1000)
	}
}

func BenchmarkMakeWith(b *testing.B) {
	b.SetBytes(1024)
	for i := 0; i < b.N; i++ {
		MakeWith(1000)
	}
}

执行结果
在这里插入图片描述

6. 数据统计

在测试开始时,会把当前内存值记录下来:
在这里插入图片描述

也就是记入testing.B.startAllocs和testing.B.startBytes,测试结束后,会用最终内存值与开始时的内存相减,
得到净增加的内存值,并记入testing.B.netAllocs和testing.B.netBytes中。
每个测试结束后,会吧结果保存到BenchmarkResult中
在这里插入图片描述

type BenchmarkResult struct {
	N         int           // 用户代码执行的次数
	T         time.Duration // 测试耗时
	Bytes     int64         // 用户代码每次处理的字节数,SetBytes设置的值
	MemAllocs uint64        // 内存对象净增加值
	MemBytes  uint64        // 内存字节净增加值
	// 附加信息
	Extra map[string]float64
}

最终统计时,只需要把净增加值除以N就能得到每次新增多少内存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/776600.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无人机便携式侦测干扰设备(定全向)技术详解

无人机便携式侦测干扰设备&#xff08;定全向&#xff09;是一种专门针对无人机进行侦测和干扰的设备。它具备定向和全向两种工作模式&#xff0c;能够覆盖较宽的频率范围&#xff0c;有效侦测并干扰无人机与遥控器之间的通信信号&#xff0c;从而达到控制或驱离无人机的目的。…

1999-2022年企业持续绿色创新水平数据

企业持续绿色创新水平数据为研究者提供了评估企业在绿色技术领域创新持续性和能力的重要视角。以下是对企业持续绿色创新水平数据的介绍&#xff1a; 数据简介 定义&#xff1a;企业持续绿色创新水平反映了企业在一定时期内绿色专利申请的持续性和创新能力。计算方法&#xf…

收银系统源码-营销活动-积分商城

1. 功能描述 营运抽奖&#xff1a;智慧新零售收银系统&#xff0c;线上商城的营销插件&#xff0c;由商户运营&#xff0c;用户通过多种渠道可以获取积分&#xff0c;不仅支持在收银端抵用&#xff0c;还可以在积分商城内兑换优惠券或者真实商品&#xff0c;提升会员活跃度&am…

计算机图形学入门24:材质与外观

1.前言 想要得到一个漂亮准确的场景渲染效果&#xff0c;不只需要物理正确的全局照明算法&#xff0c;也要了解现实中各种物体的表面外观和在图形学中的模拟方式。而物体的外观和材质其实就是同一个意思&#xff0c;不同的材质在光照下就会表现出不同的外观&#xff0c;所以外观…

CH09_JS的循环控制语句

第9章&#xff1a;Javascript循环控制语句 本章目标 掌握break关键字的使用掌握continue关键字的使用 课程回顾 for循环的特点和语法while循环的特点和语法do-while循环的特点和语法三个循环的区别 讲解内容 1. break关键字 为什么要使用break关键字 生活中&#xff0c;描…

MongoDB集群搭建-最简单

目录 前言 一、分片概念 二、搭建集群的步骤 总结 前言 MongoDB分片&#xff08;Sharding&#xff09;是一种水平扩展数据库的方法&#xff0c;它允许将数据分散存储在多个服务器上&#xff0c;从而提高数据库的存储容量和处理能力。分片是MongoDB为了应对大数据量和高吞吐量需…

13 - Python网络编程入门

网络编程入门 计算机网络基础 计算机网络是独立自主的计算机互联而成的系统的总称&#xff0c;组建计算机网络最主要的目的是实现多台计算机之间的通信和资源共享。今天计算机网络中的设备和计算机网络的用户已经多得不可计数&#xff0c;而计算机网络也可以称得上是一个“复…

idea MarketPlace插件找不到

一、背景 好久没用idea了&#xff0c;打开项目后没有lombok&#xff0c;安装lombok插件时发现idea MarketPlace插件市场找不到&#xff0c;需要重新配置代理源&#xff0c;在外网访问时通过代理服务进行连接 二、操作 ### File-->setting 快捷键 Ctrl Alt S 远端源地…

uni-app 使用Pinia进行全局状态管理并持久化数据

1.引言 最近在学习移动端的开发&#xff0c;使用uni-app前端应用框架&#xff0c;通过学习B站的视频以及找了一个开发模板&#xff0c;终于是有了一些心得体会。 B站视频1&#xff1a;Day1-01-uni-app小兔鲜儿导学视频_哔哩哔哩_bilibili B站视频2&#xff1a;01-课程和uni的…

hdu物联网硬件实验1 小灯闪烁

物联网硬件基础实验报告 学院 班级 学号 姓名 日期 成绩 实验题目 配置环境小灯 实验目的 配置环境以及小灯闪烁 硬件原理 无 关键代码及注释 /* Blink The basic Energia example. Turns on an LED on for one second, then off for one sec…

01 Web基础与HTTP协议

1.1 Web 基础 本章将介绍 Web 基础知识&#xff0c;包括域名的概念、DNS 原理、静态网页和动态网页的相关知识。 1.1.1.域名概述 1.域名的概念 ip地址不易记忆 2.早期使用host文件解析域名 主机名重复主机维护困难 3.DNS 分布式层次式 4.域名空间结构 根域顶级域 组…

在原有的iconfont.css文件中加入新的字体图标

前言&#xff1a;在阿里图标库中&#xff0c;如果你没有这个字体图标的线上项目&#xff0c;那么你怎么在本地项目中的原始图标文件中添加新的图标呢&#xff1f; 背景&#xff1a;现有一个vue项目&#xff0c;下面是这个前端项目的字体图标文件。现在需要新开发功能页&#x…

使用POI实现Excel文件的读取(超详细)

目录 一 导入poi相关的maven坐标 二 实现创建并且写入文件 2.1实现步骤 2.2实现代码 2.3效果展示 ​编辑 2.4注意 三 实现从Excel文件中读取数据 3.1实现步骤 3.2实现代码 3.3结果展示 一 导入poi相关的maven坐标 <!-- Apache poi --><dependency><gro…

【JVM-04】线上CPU100%

【JVM-04】线上CPU100% 1. 如何排查2. 再举一个例子 1. 如何排查 ⼀般CPU100%疯狂GC&#xff0c;都是死循环的锅&#xff0c;那怎么排查呢&#xff1f;先进服务器&#xff0c;⽤top -c 命令找出当前进程的运⾏列表按⼀下 P 可以按照CPU使⽤率进⾏排序显示Java进程 PID 为 2609…

LeetCode题练习与总结:排序链表--148

一、题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5]示例 3&am…

HTTP与HTTPS的主要区别

HTTP&#xff08;超文本传输协议&#xff09;与HTTPS&#xff08;超文本传输安全协议&#xff09;的主要区别在于安全性、数据传输方式、默认使用的端口以及对网站的影响。 一、安全性&#xff1a; HTTP是一种无加密的协议&#xff0c;数据在传输过程中以明文形式发送&#x…

2024年APMCM亚太杯中文赛A题——飞行器外形的优化问题

飞行器外形的优化问题 解题思路问题一第一问结果第一问代码 完整答案 本篇文章为大家分享2024年APMCM亚太杯中文赛A题——飞行器外形的优化问题的解题思路以及第一问的完整求解代码与结果&#xff0c;四问的完整解答请看文章最后&#xff01; 解题思路 飞行器是在大气层内或大…

粤港澳大湾区人工智能资本对接会”成功举办!

为促进惠州仲恺高新区人工智能产业的发展&#xff0c;推动惠深两地产业资源深度协同与合作&#xff0c;也为吸引更多的优质项目与投融资机构为惠州仲恺高新区产业发展注入动力&#xff0c;加速深圳人工智能相关产业资源落地仲恺。2024年06月26日&#xff0c;由仲恺高新区科技创…

C#用反射机制调用dll文件的字段、属性和方法

1、创建dll文件 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace CLStudent {public class Student{//字段public string Name "Tom";//属性public double ChineseScore { get; s…