go benchmark 基准测试详解

 更新时间:2023年03月31日 11:25:26   作者:qq_42170897  
这篇文章主要介绍了go benchmark 基准测试详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、benchmark的使用

1.一个简单的例子

go mod init test 创建项目test,创建目录bench/fib

创建fib.go

package fib
 
func fib(n int) int {
	if n == 0 || n == 1 {
		return n
	}
	return fib(n-1) + fib(n-2)
}

创建fib_test.go

package fib
 
import (
	"testing"
)
 
func BenchmarkFib(b *testing.B) {
	for i := 0; i < b.N; i++ {
		fib(30)
	}
}

go 不管是单元测试还是基准测试,测试函数都应该写在以_test.go 为结尾的文件中。

go 单元测试函数以Test开头,函数参数为*testing.T

go 基准测试函数以Bench开头,函数参数为*testing.B

2.运行用例

如何运行测试用例呢?

运行单元测试:go test -run=xxx,其中xxx为正则表达式,用来匹配单元测试函数的函数名。

运行基准测试:go test -bench=xxx,其中xxx为正则表达式,用来匹配基准测试函数的函数名。

上述命令只能运行当前目录中的测试用例,如果想运行其他目录的测试用例呢?

go test -bench=. test/bench/fib  ,指定目标包在项目中的绝对路径。

go test -bench=. ./fib  , 运行当前目录下的子目录fib中的测试。

go test -bench=. ./... , 运行当前目录下的所有的package中的测试。

对-bench=加上正则表达式:

go test -bench=^BenchmarkFib ./fib

3.benchmark 是如何工作的

benchmark用例的参数为b testing.B, b.N 表示要测试的内容运行的次数,这个次数对于每个用例都不同。那么这个次数变化规律是什么呢?b.N从1开始,如果内容在1s内运行结束,那么b.N会增加,测试会再次执行。b.N 的值大概以 1, 2, 3, 5, 10, 20, 30, 50, 100 这样的序列递增,越到后面,增加得越快。

修改测试的内容,让运行时间>1s

func BenchmarkFib(b *testing.B) {
	for i := 0; i < b.N; i++ {
		time.Sleep(time.Second)
		fib(30)
	}
}

 可以看到,只执行了1次。

benchmarkFib-12 的-12的意思是 GOMAXPROCS数,即cpu核数,可以使用-cpu来指定cpu核数,-cpu=2,3  表示分别使用GOMAXPROCS=2 和GOMAXPROCS=3 进行测试。

可以看到测试用例运行了两轮,分别以单核和12核,但我们的测试结果没有发生变化,因为我们的测试内容本身是单核的,与多核无缘。

4.提升准确度

对于性能测试来说,提升测试精度的一个重要手段是提升测试时间和测试次数。我们可以是用-benchtime 和 -count 来达到目的。

benchmark 默认的benchtime是1s,我们指定2s,可以看到执行次数也提升了约1倍。

我们还能直接指定b.N ,即 -benchtime=30x 表示30次

那么count就是测试的轮数了,-count=2 测试两轮。

5.内存分配情况

前面的测试结果中,只能看见执行的次数和一次执行的时间,没有任何与内存相关的信息。加入 -benchmem 就可以看到。

 因为fib函数使用的空间全在栈上,不需要进行内存分配。

下面测试切片的内存分配。创建目录 bench/cap

cap.go

// 一次性分配空间
func generateWithCap(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
}
 
// 多次分配空间
func generate(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
}

cap_test.go

package cap
 
import "testing"
 
func BenchmarkGenerateWithCap(b *testing.B) {
	for n := 0; n < b.N; n++ {
		generateWithCap(1000000)
	}
}
 
func BenchmarkGenerate(b *testing.B) {
	for n := 0; n < b.N; n++ {
		generate(1000000)
	}
}

 测试结果:

可以看到:

        一次性分配内存的切片赋值函数比多次分配内存的切片赋值函数消耗内存更少。

        一次性分配内存的切片赋值函数运行时间更少,因为内存分配需要耗时间。

6.测试不同的输入

  不同函数的复杂度不同,O(1)、O(n)、O(lgn)等,利用 benchmark 验证复杂度一个简单的方式,是构造不同的输入,对刚才的generate函数构建不同的输入可以达到这个目的。

  cap_test.go

func benchmarkGenerate(n int, b *testing.B) {
	for i := 0; i < b.N; i++ {
		generate(n)
	}
}
 
func BenchmarkGenerate1000(b *testing.B)    { benchmarkGenerate(1000, b) }
func BenchmarkGenerate10000(b *testing.B)   { benchmarkGenerate(10000, b) }
func BenchmarkGenerate100000(b *testing.B)  { benchmarkGenerate(100000, b) }
func BenchmarkGenerate1000000(b *testing.B) { benchmarkGenerate(1000000, b) }

随着输入按10倍的速度增长,运行时间也按10倍在增长,则函数的复杂度是线性的,即O(n).

二、benchmark的注意事项

1.ResetTimer

如果在正式执行测试前需要进行准备工作,那么在准备工作完成后,可以使用b.ResetTimer() 函数来重置计数器。

使用sleep模拟耗时的准备工作。

fib_test.go 

func BenchmarkFib(b *testing.B) {
	time.Sleep(time.Second)
	for i := 0; i < b.N; i++ {
		fib(30)
	}
}

每次执行fib(30)高达1s多,显然不对。

使用b.ResetTimer()

 fib_test.go

func BenchmarkFib(b *testing.B) {
	time.Sleep(time.Second)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fib(30)
	}
}

我们将耗时的准备工作排除在测试之外,每次调用fib(30)花费 6ms = 0.006s 

2.StopTimer & StartTimer

如果在每一次函数前后都需要准备工作和清理工作,那么就需要StopTimer + StartTimer 函数了。

例:

     sort_test.go

// 一次性分配空间
func generateWithCap(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
}
 
 
//冒泡排序
func bubbleSort(nums []int) {
	for i := 0; i < len(nums); i++ {
		for j := 1; j < len(nums)-i; j++ {
			if nums[j] < nums[j-1] {
				nums[j], nums[j-1] = nums[j-1], nums[j]
			}
		}
	}
}
 
func BenchmarkBubbleSort(b *testing.B) {
	for n := 0; n < b.N; n++ {
        //暂停计时
		b.StopTimer()
		nums := generateWithCap(10000)
        //继续计时
		b.StartTimer()
		bubbleSort(nums)
	}
}

显然我们只测试到排序的性能,没有将内存分配的时间花费算入结果。

每次排序需花费120ms的时间。

到此这篇关于go benchmark 基准测试的文章就介绍到这了,更多相关go 基准测试内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Go语言中Validator库的使用方法和用途

    详解Go语言中Validator库的使用方法和用途

    github.com/go-playground/validator 是一个 Go 语言的库,用于对结构体字段进行验证,它提供了一种简单而灵活的方式来定义验证规则,在这篇文章中,我们将从一个简单的问题出发,带你了解 Validator 库的用途,也会介绍Validator 的基本使用
    2023-09-09
  • 一文带你搞懂Golang如何正确退出Goroutine

    一文带你搞懂Golang如何正确退出Goroutine

    在Go语言中,Goroutine是一种轻量级线程,它的退出机制对于并发编程至关重要,下午就来介绍几种Goroutine的退出机制,希望对大家有所帮助
    2023-06-06
  • sublime text3解决Gosublime无法自动补全代码的问题

    sublime text3解决Gosublime无法自动补全代码的问题

    本文主要介绍了sublime text3解决Gosublime无法自动补全代码的问题,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • golang beego框架环境搭建过程

    golang beego框架环境搭建过程

    这篇文章主要为大家介绍了golang beego框架环境搭建的过程脚本,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • 详解如何用Golang处理每分钟100万个请求

    详解如何用Golang处理每分钟100万个请求

    在项目开发中,我们常常会遇到处理来自数百万个端点的大量POST请求,本文主要介绍了Golang实现处理每分钟100万个请求的方法,希望对大家有所帮助
    2023-04-04
  • Go 语言进阶单元测试示例详解

    Go 语言进阶单元测试示例详解

    这篇文章主要为大家介绍了Go 语言进阶单元测试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • 使用go语言实现cors中间件

    使用go语言实现cors中间件

    CORS是一种浏览器安全机制,用于控制在Web应用程序中不同源(Origin)之间的资源共享,本文将给大家介绍如何使用go语言实现cors中间件,文中有详细的代码示例供大家参考,需要的朋友可以参考下
    2023-09-09
  • 一文详解Go的面向对象编程

    一文详解Go的面向对象编程

    本文主要围绕 Golang 的 Object-oriented 所展开,介绍了其基本的面向对象的基本概念及代码实战,有需要的小伙伴跟着小编一起来学习吧
    2023-04-04
  • Go语言流程控制详情

    Go语言流程控制详情

    这篇文章主要介绍了Go语言流程控制详情,流程控制包含分三大类:条件判断,循环控制和无条件跳转。下面关于更多相关内容需要的小伙伴可以参考一下
    2022-03-03
  • golang实现枚举的几种方式

    golang实现枚举的几种方式

    在Go语言中,虽没有内置枚举类型,但可通过常量、结构体或自定义类型和方法实现枚举功能,这些方法提高了代码的可读性和维护性,避免了魔法数字的使用,感兴趣的可以了解一下
    2024-09-09

最新评论