【Go高性能】测试(单元测试、基准测试)

news2024/9/30 23:05:39

Go测试

  • 一、分类
    • 1. 单元测试
    • 2. 基准测试
  • 二、基准测试
    • 1. 介绍
    • 2. 基准测试基本原则
    • 3. 使用testing包构建基准测试
      • 3.1 执行基准测试
      • 3.2 基准测试工作原理
      • 3.3 改进基准测试的准确性
        • 3.3.1 -benchtime
        • 3.3.2 -count
        • 3.3.3 -cpu
    • 4. 使用benchstat工具比较基准测试(可跳过)
      • 4.1 对比标准 benchmarks 和 benchstat
    • 5. 避免基准测试的启动耗时
    • 6. 基准测试的内存分配
  • 三、单元测试
    • 1. Goland自动生成
    • 2. 常用断言方法


介绍如何使用 Go 语言的标准库 testing 进行测试。

参考:①https://cloud.tencent.com/developer/article/2211864
②https://geektutu.com/post/quick-go-test.html#7-Benchmark-%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95

一、分类

在Go语言的世界里,testing包是进行单元测试和基准测试的核心组件。它不仅简化了测试流程,还通过简洁明了的API鼓励开发者编写高质量的测试代码。

Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾。

1. 单元测试

  • 功能: 测试单个函数/方法,报告测试失败、记录日志、设置测试状态等。
  • 示例: 在 func TestXxx(t *testing.T) 中使用,适用于验证代码的正确性。

2. 基准测试

  • 功能:用于基准测试(性能测试),记录执行时间和内存分配等性能指标。
  • 示例: 在 func BenchmarkXxx(b *testing.B) 中使用,适用于测量代码的性能。

二、基准测试

1. 介绍

要想改进程序的性能,首先要知道程序的当前性能。
benchmark 是 go 语言中用于测试基准性能的工具。该工具用于测试被测试函数的平均运行耗时、内存分配次数。主要适用于在已知性能瓶颈在哪里时的场景。通过对相同功能函数的不同实现的性能指标(平均运行耗时、平均内存分配次数)进行比较,以判断性能的优劣。

2. 基准测试基本原则

为了保证基准测试结果的相对稳定性,需要保持硬件环境的稳定。即:

  • 机器处于空闲状态
  • 机器关闭了节能模式
  • 避免使用虚拟机和云主机

不过如果单纯测试某个函数的性能,精准性要求也不高的话,没必要这么细致啦~

3. 使用testing包构建基准测试

Go的tesing包中内置了基准测试功能。在编写基准测试时基本和编写单元测试的原则相似:

  • 文件名必须以 _test.go 为后缀
  • 函数名必须以 BenchmarkXxxx开头
  • 基准测试函数的参数类型是 *Testing.B,而非 *Testing.T

需要测试函数:

func singleFunc() {
	time.Sleep(100 * time.Millisecond)
}

基准测试代码:

func Benchmark_singleFunc(b *testing.B) {
	for n := 0; n < b.N; n++ {
		singleFunc()
	}
}

3.1 执行基准测试

  1. 方法一:直接点击方法左侧运行
    在这里插入图片描述
  2. 方法二:使用 go test -bench=要测的方法名 ./方法所在包的相对路径
    例如:go test -bench=singleFunc(已在函数所在包的路径下)
  • -bench 标记使用正则表达式来匹配要运行的基准测试函数名称。所以,最常用的方式是通过-bench . 标记来执行该包下的所有的基准函数。
  • 默认情况下,执行go test命令时 只会执行单元测试,而基准测试会被排除在外。所以,需要在 go test 命令中添加 -bench 标记,以执行基准测试。

基准测试报告每一列值对应的含义如下:

type BenchmarkResult struct {
    N         int           // 迭代次数
    T         time.Duration // 基准测试花费的时间
    Bytes     int64         // 一次迭代处理的字节数
    MemAllocs uint64        // 总的分配内存的次数
    MemBytes  uint64        // 总的分配内存的字节数
}

测试结果:

1. goos: windows
2. goarch: amd64
3. pkg: method/f
4. Benchmark_singleFunc
5. Benchmark_singleFunc-6                10         101990650 ns/op
5. PASS

关注指标:

  1. 第5行Benchmark_singleFunc-6 10 101990650 ns/op
  • Benchmark_singleFunc: 这是基准测试的名称,表示测试的函数名。
  • -6: 这是运行基准测试的 goroutine 数量(并发数)。和运行该测试用例时的GOMAXPROCS值有关系,默认为启动时 Go 进程可见的 CPU 数。在这个例子中,6表示使用了 6 个 goroutine 来并行执行基准测试。
  • 10:是基准测试在测试期间执行的总次数(b.N 的值)。
  • 101990650 ns/op:是每次操作的平均耗时,单位是纳秒(ns)。

3.2 基准测试工作原理

每个基准函数被执行时都有一个b.N值,该值是由go运行时自动生成的, 代表基准函数应该执行的次数。

b.N 从 1 开始,基准函数默认要运行 1 秒,如果该函数的执行时间在 1 秒内就运行完了,那么就递增 b.N 的值,再重新再执行一次。

3.3 改进基准测试的准确性

3.3.1 -benchtime

基准测试运行的时间越长,迭代次数越多,最终的平均值结果越准确。

如果你的基准测试只执行了 100 次或 10 次迭代,那么最终得出的平均值可能会偏高。如果你的基准测试执行了上百万或十亿次迭代,那么得出的平均耗时将会非常准确。

可以使用 -benchtime 标识指定基准测试执行的时间以调整迭代次数(即b.N的值),以便得到更准确的结果。例如:

PS E:\Program Data\GoProject\basic\method\f> go test -bench=singleFunc -benchtime=10s
goos: windows
goarch: amd64
pkg: method/f
Benchmark_singleFunc-6               100         103430246 ns/op
PASS
ok      method/f        13.644s

执行以上命令,直到其达到 b.N 的值需要花费超过 10 秒的时间才能返回。由于我们的运行时间增加了 10 倍,因此迭代的总次数也增加了 10 倍。结果(每次操作耗时 103430246ns/op) 没有太大的变化,说明我们的数据相对比较稳定,是我们所期望的。
如果你有一个基准测试运行了数百万次或数十亿次迭代,你可能会发现基准值不稳定,因为你的机器硬件的散热性能、内存局部性、后台进程、gc 等因素都会影响函数执行的时间。

3.3.2 -count

通过 -count 标志,可以指定基准测试跑多次,以消除上述的不稳定因素:

 go test -bench . -benchtime 2s -count 5 
goos: windows
goarch: amd64
pkg: method/f
Benchmark_singleFunc-6                22         103056855 ns/op
Benchmark_singleFunc-6                21         102299486 ns/op
Benchmark_singleFunc-6                22         104436250 ns/op
Benchmark_singleFunc-6                21         102005167 ns/op
Benchmark_singleFunc-6                21         102501433 ns/op
PASS
ok      method/f        14.764s
3.3.3 -cpu
go test -bench . -cpu 1,2,4            
goos: windows
goarch: amd64
pkg: method/f
Benchmark_singleFunc                  10         102037000 ns/op
Benchmark_singleFunc-2                10         101757150 ns/op
Benchmark_singleFunc-4                10         102318930 ns/op
PASS
ok      method/f        6.607s

该示例展示了分别用 CPU 为 1 核、2 核、4 核时运行基准测试的结果。

4. 使用benchstat工具比较基准测试(可跳过)

由于基准测试受电源管理、后台进程、散热的影响,所以对于任何一个基准测试来说,运行多次来求平均值是一个非常好的建议。

下面介绍一个由 Russ Cox 编写的工具:benchstat

% go get golang.org/x/perf/cmd/benchstat

benchstat 可以对一组基准测试的结果求平均值,并显示出对应的稳定性。这是函数在使用电池的电脑上执行的基准示例:

% go test -bench . -benchtime 2s -count 5 | tee old.txt
goos: windows
goarch: amd64
pkg: method/f
Benchmark_singleFunc-6                22         103056855 ns/op
Benchmark_singleFunc-6                21         102299486 ns/op
Benchmark_singleFunc-6                22         104436250 ns/op
Benchmark_singleFunc-6                21         102005167 ns/op
Benchmark_singleFunc-6                21         102501433 ns/op
PASS
ok      method/f        14.764s

% benchstat old.txt
name     time/op
singleFunc-6  102.5ms ± 2%

平均操作耗时是102.5毫秒,并且误差在 +/-2%。

4.1 对比标准 benchmarks 和 benchstat

确定两组基准测试结果之间的差异可能是单调乏味且容易出错的。 Benchstat 可以帮助我们解决这个问题。

提示 : 保存基准运行的输出很有用,但你也可以保存生成它的二进制文件。 为此,请使用-c标志来保存测试二进制文件;我经常将这个二进制文件从.test重命名为.golden。

% go test -c
% mv fib.test fib.golden 

为了比较新版本,我们编译了一个新的测试二进制文件并对它们都进行了基准测试,并使用benchstat对输出进行比较。

% go test -c
% ./fib.golden -test.bench=. -test.count=10 > old.txt
% ./fib.test -test.bench=. -test.count=10 > new.txt
% benchstat old.txt new.txt
name     old time/op  new time/op  delta
Fib20-8  44.3µs ± 6%  25.6µs ± 2%  -42.31%  (p=0.000 n=10+10)

比较基准测试时需要检查三件事

  • 新老两次的方差。1-2% 是不错的, 3-5% 也还行,但是大于5%的话,可能不太可靠。 在比较一方具有高差异的基准时要小心,您可能看不到改进。
  • p值。p值低于0.05是比较好的情况,大于0.05则意味着基准测试结果可能没有统计学意义。
  • 样本不足。benchstat将报告它认为有效的新旧样本的数量,有时你可能只发现9个报告,即使你设置了-count=10。拒绝率小于10%一般是没问题的,而高于10%可能表明你的设置是不稳定的,也可能是比较的样本太少了。

5. 避免基准测试的启动耗时

有时候基准测试每次执行的时候会有一次启动配置耗时。b.ResetTimer() 函数可以用于忽略启动的累积耗时。如下

func BenchmarkExpensive(b *testing.B) {
    boringAndExpensiveSetup() //启动配置。默认这里的执行时间是被计算在内的
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        //function under test
    }
}

在上例代码中,使用 b.ResetTimer() 函数重置了基准测试的计时器

如果在每次循环迭代中,你有一些费时的配置逻辑,要使用 b.StopTimer()b.StartTimer() 函数来暂定基准测试计时器。

func BenchmarkComplicated(b *testing.B) {
    for n := 0; n < b.N;n++ {
        b.StopTimer()
        complicatedSetup()
        b.StartTimer()
        //function under test
    }
}

6. 基准测试的内存分配

内存分配的次数和分配的大小跟基准测试的执行时间相关。在基准测试中有两种方式可以记录并输出内存分配:

  • 在代码中增加 b.ReportAllocs() 函数来告诉 testing 框架记录内存分配的数据。
  • 在go test命令中添加 -benchmem 标识来强制 testing 框架打印出所有基准测试的内存分配次数

方式一:代码中添加 b.ReportAllocs()

func BenchmarkRead(b *testing.B) {
    b.ReportAllocs()
    for n := 0; n < b.N; n++ {
        //function under test
    }
}

方式二:go test命令中添加 -benchmem标识

%  go test -run=^$ -bench=. -benchmem bufio
goos: darwin
goarch: amd64
pkg: bufio
BenchmarkReaderCopyOptimal-8            13860543                82.8 ns/op            16 B/op          1 allocs/op
BenchmarkReaderCopyUnoptimal-8           8511162               137 ns/op              32 B/op          2 allocs/op
BenchmarkReaderCopyNoWriteTo-8            379041              2850 ns/op           32800 B/op          3 allocs/op
BenchmarkReaderWriteToOptimal-8          4013404               280 ns/op              16 B/op          1 allocs/op
BenchmarkWriterCopyOptimal-8            14132904                82.7 ns/op            16 B/op          1 allocs/op
BenchmarkWriterCopyUnoptimal-8          10487898               113 ns/op              32 B/op          2 allocs/op
BenchmarkWriterCopyNoReadFrom-8           362676              2816 ns/op           32800 B/op          3 allocs/op
BenchmarkReaderEmpty-8                   1857391               639 ns/op            4224 B/op          3 allocs/op
BenchmarkWriterEmpty-8                   2041264               577 ns/op            4096 B/op          1 allocs/op
BenchmarkWriterFlush-8                  87643513                12.5 ns/op             0 B/op          0 allocs/op
PASS
ok      bufio   13.430s

第四列是每次操作的平均内存分配大小,单位是字节(B)。
第五列是每次操作的平均内存分配次数(allocations)。

三、单元测试

单元测试通常放置在与被测试文件同目录下的_test.go文件中。测试函数必须以Test开头,后接被测试函数名,接受一个t *testing.T参数。

1. Goland自动生成

  1. 把鼠标定在要测试的方法上面,右击选Generate,
    在这里插入图片描述
    在这里插入图片描述
  2. 生成测试文件
    在这里插入图片描述
  3. 增加测试数据
    在这里插入图片描述
  4. 运行测试
    可以直接运行全部的测试方案,也可以自己选择想要运行的测试方案。

2. 常用断言方法

  • t.Errort.Fatal:报告错误,后者还会终止测试。
  • t.Logf:记录日志信息。
  • t.Errorf:当条件不满足时,记录错误并继续执行后续测试。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode 第 408 场周赛题解

Leetcode 第 408 场周赛题解 Leetcode 第 408 场周赛题解题目1&#xff1a;3232. 判断是否可以赢得数字游戏思路代码复杂度分析 题目2&#xff1a;3233. 统计不是特殊数字的数字数量思路代码复杂度分析 题目3&#xff1a;3234. 统计 1 显著的字符串的数量思路代码复杂度分析 题…

Pycharm can‘t open file ‘D:\\Program‘: [Errno 2] No such file or directory

问题描述 Pycharm 使用Python 3.11.9 版本调试代码报错&#xff1a; 解决方案 1、WindowsR&#xff0c;调起CMD&#xff08;PowerShell不行&#xff09;&#xff0c;执行以下指令&#xff1a; mklink /J "D:\PyCharm" "D:\Program Files\JetBrains\PyCharm 2…

react学习之useState和useEffect

useState useState 可以使函数组件像类组件一样拥有 state&#xff0c;函数组件通过 useState 可以让组件重新渲染&#xff0c;更新视图。 实际使用 setstate()中回调函数的返回值将会成为新的state值回调函数执行时&#xff0c; React会将最新的state值作为参数传递 const A…

Vulkan进阶系列1 - Raytracing 光线查询

一:概述 为了提高效率,光线追踪需要将几何体组织成加速结构(AS, 即Acceleration Structure),以减少渲染过程中光线与三角形的相交测试次数。这种层次结构通常在硬件中实现,但只有两个层级对用户可见:一个顶层加速结构(TLAS),它引用任意数量的底层加速结构(BLAS)。通…

Linux进程间的通信(一)exec函数族,getenv获取系统环境变量,system和popen的区别,文件和记录锁定通信

目录 几个系统关键api exec函数族 getenv() system() 文件和记录锁定通信 在Linux/Unix系统中&#xff0c;进程间通信方式&#xff08;Inter-Process Comunication&#xff09;通常有如下若干中方式&#xff1a; 1、文件和记录锁定 2、管道 3、信号 4、system-V 5、PO…

贪心算法三道经典题(买卖股票,分发饼干)

贪心算法 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 步骤&#xff1a; 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优解将局部最优解堆叠成全局最优解 分发饼干 LeetCode原题 找满足孩子数量的最大值———最优解问题 什么…

数据同步大冒险:PostgreSQL到MySQL的奇妙之旅

引言&#xff1a;一场跨数据库的浪漫邂逅 &#x1f491; 在数据的世界里&#xff0c;不同数据库系统就像是来自不同星球的恋人&#xff0c;它们各自拥有独特的魅力&#xff0c;但偶尔也会渴望一场跨越界限的亲密接触。今天&#xff0c;我们就来见证一场PostgreSQL与MySQL之间的…

初赛试题-2022年CSP-J3

先言 本次试卷 完善程序 三、完善程序&#xff08;每题3分&#xff0c;共30分&#xff09; &#xff08;1&#xff09; 【答案】 A B C D A &#xff08;2&#xff09; 【答案】 A B C D A

探索用于小占用关键词检测的TinyML框架:一个简明概述

目录 摘要 第一部分&#xff1a;引言 第二部分&#xff1a;部署TinyML的常见挑战 第三部分&#xff1a;SF-KWS的不同方法 A. 网络架构&#xff08;Network Architecture&#xff09; B. 学习技术&#xff08;Learning Techniques&#xff09; C. 模型压缩&#xff08;Mo…

最新黑名单查询录入系统PHP网站源码

源码介绍&#xff1a; 最新黑名单查询录入系统PHP网站源码 前端html 后端layui 操作部分都采用API接口的方式实线 集结了layui表格的多数据操作&#xff0c;添加&#xff0c;批量删除&#xff0c;分页&#xff0c;单项删除 后台数据修改采用绑定参数的形式来进行修改可以很…

C语言函数递归(含扫雷进阶思路)

文章目录 一、什么是递归二、递归的使用思路和限制条件1.递归的使用思路2.递归的限制条件 三、递归的举例举例1&#xff1a;求n的阶乘2.举例2&#xff1a;顺序打印⼀个整数的每⼀位 四、递归与迭代对比五、递归与迭代对比举例七、扫雷进阶思路 一、什么是递归 递归是学习C语⾔函…

暄桐教室分享“闲人”指南

一种理想的生活状态&#xff0c;叫“做个闲人”&#xff0c;如苏东坡《行香子述怀》那般&#xff0c;“对一张琴&#xff0c;一壶酒&#xff0c;一溪云”&#xff0c;放下纷扰&#xff0c;好自在。然而&#xff0c;闲并不是简单的无事可做&#xff0c;让自己时光充沛、能量聚集…

MacOS使用FileZilla通过ssh密钥文件连接远程服务器(已解决)

需求描述 mac电脑,使用filezilla通过FTP连接远程服务器,使用ssh密钥文件代替密码。 版本信息 MacOS:Sonoma 14.5 M3芯片 FileZilla:3.66.5 在这里插入图片描述 连接 1. 创建站点 打开filezilla工具,右上角选择“文件 -> 站点管理器”,打开站点管理器弹窗。 2.…

vue 动态替换父组件

替换父组件&#xff1f;&#xff1f; 什么鬼&#xff1f;&#xff1f;&#xff1f; 这个场景的确很少见&#xff01;&#xff01;不过我们要说的的确是要替换父组件&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 就是子组件内容不变但是父组件变…

【HuggingFace Transformers】LlamaDecoderLayer源码解析

LlamaDecoderLayer源码解析 1. LlamaDecoderLayer 介绍2. LlamaDecoderLayer 类源码解析 1. LlamaDecoderLayer 介绍 LlamaDecoderLayer 是 LLaMA 模型中的一个关键组件&#xff0c;它结合了自注意力机制、全连接层和残差连接&#xff0c;以及对输入数据的归一化。主要流程为&…

SpringCloud之一IDEA导入已有微服务项目并启动服务

一|、导入已有微服务项目 启动idea&#xff0c;file --> open&#xff0c;选择项目根目录&#xff1b;点击屏幕右侧maven projects按钮 -->点那个绿&#xff0c;如果屏幕右侧没有maven projects按钮&#xff0c;点击Help->Find Action&#xff0c;输入maven&#xff…

算法-存在重复元素(219)

这道题一眼看过去暴力&#xff0c;两层循环&#xff0c;找到相等的数字&#xff0c;然后判断一下就行&#xff0c;但是这样的话不符合哈希表使用原则。这道题同样利用了hash表键值配对的规则。 class Solution {public boolean containsNearbyDuplicate(int[] nums, int k) {M…

C语言 | Leetcode C语言题解之第382题链表随机节点

题目&#xff1a; 题解&#xff1a; typedef struct {struct ListNode * head; } Solution;Solution* solutionCreate(struct ListNode* head) {Solution * obj (Solution *)malloc(sizeof(Solution));assert(obj ! NULL);obj->head head;return obj; }int solutionGetRa…

keil中内存的存储规律

keil中内存的存储规律 keil中内存的存储规律 文章目录 keil中内存的存储规律keil中内存的存储规律 keil中内存的存储规律 #include <stdlib.h> #include "gd32f30x.h" #include "led_drv.h" #include "delay.h" #include "key_drv.…