前言

基准测试(benchmark)是 go testing 库提供的,用来度量程序性能,算法优劣的利器。

在日常生活中,我们使用速度 m/s(单位时间内物体移动的距离)大小来衡量一辆跑车的性能,同理,我们可以使用”单位时间内程序运行的次数“来衡量程序的性能。

在日常开发中,如果和同事在代码实现上有分歧,不用多费口舌,跑个分就知道谁牛X。

注意:在进行基准测试时,硬件资源直接影响测试结果,为了保证测试结果的可重复性,需要尽可能地保证硬件资源一致。(单一变量原则)

快速开始

创建项目 learnGolang

mkdir learnGolang
cd learnGolang
go mod init learnGolang

创建文件 main.go,编写我们的被测函数

package main

// 斐波那契数列
func fib(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2)
} func sum(a, b int) int {
return a + b
}

创建文件 main_test.go ,编写基准测试用例

package main

import "testing"

func BenchmarkFib10(b *testing.B) {
for n := 0; n < b.N; n++ {
fib(10)
}
} func BenchmarkFib20(b *testing.B) {
for n := 0; n < b.N; n++ {
fib(20)
}
} func BenchmarkSum(b *testing.B) {
for n := 0; n < b.N; n++ {
sum(1, 2)
}
}
  • 位于同一个 package 内的测试文件以 _test.go 结尾,其中的测试用例格式为 func BenchmarkXxx(b *testing.B) ,注意 Xxx 首字母要大写(即驼峰命名法)
  • 函数内被测函数循环执行 b.N 次

开始运行

$ go test -bench=. .
goos: windows
goarch: amd64
pkg: learnGolang
BenchmarkFib10-4 3360627 362 ns/op
BenchmarkFib20-4 26676 44453 ns/op
BenchmarkSum-4 1000000000 0.296 ns/op
PASS
ok learnGolang 3.777s
  • go test [packages] 指定测试范围
方法一 方法二
运行当前 package 内的用例 go test packageName go test .
运行子 package 内的用例 go test packageName/subName go test ./subName
递归运行所有的用例 go test packageName/... go test ./...
  • go test 命令默认不执行 benchmark 测试,需要加上 -bench 参数,该参数支持正则表达式,只有匹配到的测试用例才会执行,使用 . 则运行所有测试用例
# 只运行斐波那契数列测试用例
$ go test -bench='.*Fib.*' .
goos: windows
goarch: amd64
pkg: learnGolang
BenchmarkFib10-4 3287449 357 ns/op
BenchmarkFib20-4 27097 44461 ns/op
PASS
ok learnGolang 3.418s
  • BenchmarkFib10-4 中的 4 即 GOMAXPROCS,默认等于 CPU 核数

  • 3287449 357 ns/op 表示单位时间内(默认是1s)被测函数运行了 3287449 次,每次运行耗时 357ns,

    3287449*357ns=1.173s(耗时比 1s 略多,因为测试用例执行、销毁等是需要时间的)

  • ok learnGolang 3.418s 表示本次测试总耗时

进阶参数

-benchtime t

在高中物理学中,由于测试物体瞬时速度不好实现,我们可以让物体多移动一段时间,然后采用“总距离/时间段”算出平均速度来代替瞬时速度。

go benchmark 默认测试时间是 1s,同样的原理,为了提升测试准确度,我们可以使用该参数适当增加时长。

➜  learnGolang go test -bench='Fib10$'
goos: linux
goarch: amd64
pkg: learnGolang
BenchmarkFib10-12 4153650 288 ns/op
PASS
ok learnGolang 1.491s
# 指定时长为 5s
➜ learnGolang go test -bench='Fib10$' -benchtime=5s
goos: linux
goarch: amd64
pkg: learnGolang
BenchmarkFib10-12 20616992 288 ns/op
PASS
ok learnGolang 6.235s

还是高中物理学,我们也可以指定物理移动的距离,然后测量所耗费的时间,计算平均速度。

该参数还支持特殊的形式 Nx ,用来指定被测程序的运行次数。

# 指定运行次数为 1000 次
➜ learnGolang go test -bench='Fib10$' -benchtime=1000x
goos: linux
goarch: amd64
pkg: learnGolang
BenchmarkFib10-12 1000 305 ns/op
PASS
ok learnGolang 0.002s

-count n

同样类似与测量物体速度,为了提升精确度,我们多做几次测试。

➜  learnGolang go test -bench='Fib10$' -benchtime=5s -count=3
goos: linux
goarch: amd64
pkg: learnGolang
BenchmarkFib10-12 19596388 288 ns/op
BenchmarkFib10-12 20796957 290 ns/op
BenchmarkFib10-12 20492478 291 ns/op
PASS
ok learnGolang 18.542s

-cpu n

该参数可以设置 benchmark 所使用的 CPU 核数。

下面我们模拟一次多核并行计算的例子,并观察设置不同核数后的测试结果

// main.go
func parallelExam() int {
chs := make([]chan int, 10) // 设置 10 个协程去并行计算
for i := 0; i < len(chs); i++ {
chs[i] = make(chan int, 1)
go parallelSum(chs[i])
}
sum := 0
for _, ch := range chs {
res := <-ch
sum += res
}
return sum
} func parallelSum(ch chan int) {
defer close(ch)
sum := 0
for i := 1; i <= 100000; i++ { // 10万
sum += i
}
ch <- sum
}
// main_test.go
func BenchmarkParallelExam(b *testing.B) {
for n := 0; n < b.N; n++ {
parallelExam()
}
}
➜  learnGolang go test -bench='BenchmarkParallelExam' -cpu=1,4,6,10,12
goos: linux
goarch: amd64
pkg: learnGolang
BenchmarkParallelExam 3154 366754 ns/op
BenchmarkParallelExam-4 9316 119747 ns/op
BenchmarkParallelExam-6 10000 107040 ns/op
BenchmarkParallelExam-10 10000 108144 ns/op
BenchmarkParallelExam-12 9891 110018 ns/op
PASS
ok learnGolang 5.604s

从运行结果看出,随着 CPU 核数的增加,性能逐步提升,但是到一定阈值后,性能趋于稳定,此时再增加 CPU 核数,性能反而下降,因为 CPU 核心之间的切换也是需要成本的。

-benchmem

除了速度,内存分配情况也是需要我们重点关注的指标。

go 语言中,slice 有一个 cap 属性,合理的设置该值,可以减少内存分配次数,分配大小,提升程序性能。

// main.go
func sliceNoCap() {
s := make([]int, 0) // 未设置 cap 值
for i := 0; i < 10000; i++ {
s = append(s, i)
}
} func sliceWithCap() {
s := make([]int, 0, 10000) // 预先设置 cap 值
for i := 0; i < 10000; i++ {
s = append(s, i)
}
}
// main_test.go
func BenchmarkSliceNoCap(b *testing.B) {
for n := 0; n < b.N; n++ {
sliceNoCap()
}
} func BenchmarkSliceWithCap(b *testing.B) {
for n := 0; n < b.N; n++ {
sliceWithCap()
}
}
➜  learnGolang go test -bench='Cap$' -benchmem .
goos: linux
goarch: amd64
pkg: learnGolang
BenchmarkSliceNoCap-12 31318 38614 ns/op 386297 B/op 20 allocs/op
BenchmarkSliceWithCap-12 111764 10269 ns/op 81920 B/op 1 allocs/op
PASS
ok learnGolang 2.858s

可以看到前者每次执行会分配 386297 字节的内存,约等于后者的 3.76 倍,每次执行会分配内存 20 次,是后者的 20 倍。

注意事项

ResetTimer

If a benchmark needs some expensive setup before running, the timer may be reset

如果在整个 benchmark 执行前,需要一些耗时的准备工作,我们需要将这部分耗时忽略掉

func BenchmarkFib(b *testing.B) {
time.Sleep(3 * time.Second) // 模拟耗时的准备工作
b.ResetTimer() // 重置计时器,忽略前面的准备时间
for n := 0; n < b.N; n++ {
fib(10)
}
}

StopTimer & StartTimer

StopTimer stops timing a test. This can be used to pause the timer while performing complex initialization that you don't want to measure.

StartTimer starts timing a test. This function is called automatically before a benchmark starts, but it can also be used to resume timing after a call to StopTimer.

如果在被测函数每次执行前,需要一些准备工作,我们可以使用 StopTimer 暂停计时,准备工作完成后,使用 StartTimer 继续计时。

func BenchmarkFib(b *testing.B) {
for n := 0; n < b.N; n++ {
b.StopTimer() // 暂停计时
prepare() // 每次函数执行前的准备工作
b.StartTimer() // 继续计时 funcUnderTest() // 被测函数
}
}

参考

Go 语言高性能编程 - benchmark 基准测试

Go Package Testing

Go Testing flags

High Performance Go Workshop

Go benchmark 详解的更多相关文章

  1. 负载均衡之Haproxy配置详解(及httpd配置)

    下图描述了使用keepalived+Haproxy主从配置来达到能够针对前段流量进行负载均衡到多台后端web1.web2.web3.img1.img2.但是由于haproxy会存在单点故障问题,因此使 ...

  2. Protocol Buffers编码详解,例子,图解

    Protocol Buffers编码详解,例子,图解 本文不是让你掌握protobuf的使用,而是以超级细致的例子的方式分析protobuf的编码设计.通过此文你可以了解protobuf的数据压缩能力 ...

  3. iOS开发——UI篇OC篇&SpriteKit详解

    SpriteKit详解 SpriteKit,iOS/Mac游戏制作的新纪元 这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看这篇总览.本文仅作为个人记录使用,也欢迎在许可协议范围内转载或 ...

  4. Linux下的I/O复用与epoll详解

    前言 I/O多路复用有很多种实现.在linux上,2.4内核前主要是select和poll,自Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器的必备技术.尽管 ...

  5. epoll机制详解

    epoll机制详解 大牛的详解 epoll详解 什么是epoll? epoll是为处理大批量句柄而作了改进的poll, 是性能最好的多路I/O就绪通知方法; 只有三个系统调用: epoll_creat ...

  6. vue和react全面对比(详解)

    vue和react对比(详解) 放两张图镇压小妖怪 本文先讲共同之处, 再分析区别 大纲在此: 共同点: a.都使用虚拟dom b.提供了响应式和组件化的视图组件 c.注意力集中保持在核心库,而将其他 ...

  7. (Dos)/BAT命令入门与高级技巧详解(转)

    目录 第一章 批处理基础 第一节 常用批处理内部命令简介 1.REM 和 :: 2.ECHO 和 @ 3.PAUSE 4.ERRORLEVEL 5.TITLE 6.COLOR 7.mode 配置系统设 ...

  8. 守护客户数据价值:企业级NewSQL HTAP分布式云TBase架构详解

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 作者:jasonys,隶属于腾讯技术工程事业群数据平台部,负责TBase数据的技术研发和架构设计,有超过10年的数据库内核开发设计经验,完成 ...

  9. (转)Linux下select, poll和epoll IO模型的详解

    Linux下select, poll和epoll IO模型的详解 原文:http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll ...

随机推荐

  1. B - 真·签到题 FZU - 2214(背包)

    Given a set of n items, each with a weight w[i] and a value v[i], determine a way to choose the item ...

  2. 拿到蚂蚁金服的offer是一种什么体验?3年Java程序员分享面经

    前言:我是一名三年的Java程序员,之前一直是在外包公司工作的.在这个月五号的时候,通过我的学长做内推,有了去蚂蚁金服面试的机会.我是在12号接到的电话面试的,因为蚂蚁金服需要7天的简历评估的.还有就 ...

  3. 1-2 postman工具简介

    postman提供了一个多窗口和多选项卡页面用于发送和接受请求,postman努力保持整洁和灵活,提供更多的空间,满足用户的需要.他很简单,能满足大部分接口的测试,性价比特别高.如图所示:  1.侧边 ...

  4. Python 使用oslo.vmware管理ESXI虚拟机

    oslo.vmware是OpenStack通用框架中的一部分,主要用于实现对虚拟机的管理任务,借助oslo.vmware模块我们可以管理Vmware ESXI集群环境. 读取所有节点主机 from o ...

  5. hdu3768 spfa+全排列

    题意:       给你一个无向图,和一些必须经过的点,问你从起点出发,到达所有必须经过的点再回来的最小总路径. 思路:       因为必须经过的点的数量很小,小于等于10,全排列是 10! = 3 ...

  6. c/c++ 指针数组 和 数组指针

    看这个标题都要晕了,我们不妨把他拆开来理解,比较容易 指针数组:对象是一个数组,数组元素的类型是指针 指针数组的定义方式: 类型名 *数组名[数组长度]; 如: int *p[8]; 数组指针:对象是 ...

  7. shell中的引号和转义

    引号和转义 Bash 只有一种数据类型,就是字符串.不管用户输入什么数据,Bash 都视为字符串.因此,字符串相关的引号和转义,对 Bash 来说就非常重要. 转义 某些字符在 Bash 里面有特殊含 ...

  8. 索引器和ref、out关键字

    这节讲三个小知识:索引器.ref.out. 索引器: 在一个类中,我们可以定义一个索引器,它可以让我们在外部像访问数组元素一样访问类的属性成员. 索引器的定义就像定义属性一样,只不过名称为this,后 ...

  9. 一款好用的数据血缘关系在线工具--SQLFlow

      l  数据血缘关系(data lineage) 数据血缘属于数据治理中的一个概念,是在数据溯源的过程中找到相关数据之间的联系,它是一个逻辑概念.数据治理中经常提到血缘分析,血缘分析是保证数据融合的 ...

  10. 2021.02.21cf补题

    B. National Project 题意:总长度为n的公路进行维修,天气是有规律性的,连续g天的好天气,连续b天的坏天气,必须在好天气进行维护,问至少维护n的一半,那么至少需要多少天 思路:必须是 ...