Go benchmark 一清二楚
前言
基准测试(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 一清二楚的更多相关文章
- mysql benchmark基准测试
git项目地址: https://github.com/akopytov/sysbench 利用sysbench很容易对mysql做性能基准测试(当然这个工具很强大,除了测试主流数据库性能,还能测试其 ...
- Kafka设计解析(五)- Kafka性能测试方法及Benchmark报告
本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/12/31/KafkaColumn5_kafka_benchmark 摘要 本文主要介绍了如何利用 ...
- Azure Redis Cache (3) 在Windows 环境下使用Redis Benchmark
<Windows Azure Platform 系列文章目录> 熟悉Redis环境的读者都知道,我们可以在Linux环境里,使用Redis Benchmark,测试Redis的性能. ht ...
- CI框架源码阅读笔记5 基准测试 BenchMark.php
上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功 ...
- Multiple sequence alignment Benchmark Data set
Multiple sequence alignment Benchmark Data set 1. 汇总: 序列比对标准数据集: http://www.drive5.com/bench/ This i ...
- [转] CentOS单独安装Apache Benchmark压力测试工具的办法
Apache安装包中自带的压力测试工具 Apache Benchmark(简称ab) 简单易用,这里就采用 ab作为压力测试工具了. 1.独立安装 ab运行需要依赖apr-util包,安装命令为: 1 ...
- Visual Tracker Benchmark
直接的方法: 首先将代码先拷到benchmark_v1.0/tackers/这个文件夹下,你会发现里面已有好几个算法的代码文件夹了. 这边注意了,我就是这样的,没有注意把代码拷贝进去之后要自己写一个调 ...
- benchmark
redis benchmark How many requests per second can I get out of Redis? Using New Relic to Understand R ...
- STREAM Benchmark
STREAM Benchmark及其操作性能分析 文/raywill STREAM 是业界广为流行的综合性内存带宽实际性能 测量 工具之一.随着处理器处理核心数量的增多,内存带宽对于提升整个系统性能越 ...
随机推荐
- Codeforces Round #613 (Div. 2) B. Just Eat It! (DP)
题意:有一个长度为\(n\)的序列,找出最大的长度不为\(n\)的子段和,问最大子段和是否小于所有元素和. 题解:最大子段和我们可以直接用dp来找,每次状态转移为:\(dp[i]=max(dp[i-1 ...
- 牛客编程巅峰赛S1第5场 - 青铜&白银 C.排队 (优先队列,归并排序)
题意:有\(m\)个窗口,\(n\)个人排队,每个人都有各自的办理时间,只有办理完成窗口才能空出来,后面的人开始办理,求有多少人比后面的人开始办理的早但完成的晚. 题解:我们可以用优先队列来模拟办理, ...
- Python 实现多线程的几种方式
threading.Thread 模块 继承实现: import threading import time class TestThread(threading.Thread): def __ini ...
- Linux 搭建网站
wget http://dl.wdlinux.cn/lanmp_laster.tar.gz tar zxvf lanmp_laster.tar.gz sh lanmp.sh https://www.w ...
- JVM你了解?
1.谈谈你对JAVA的理解 平台无关性(一次编译,到处运行) GC(不必手动释放堆内存) 语言特性(泛型.lambda) 面向对象(继承,封装,多态) 类库 异常处理 2.平台无关性怎么实现
- K8S(07)交付实战-架构说明并准备zk集群
k8s交付实战-架构说明并准备zk集群 目录 k8s交付实战-架构说明并准备zk集群 1 交付的服务架构图: 1.1 架构图解 1.2 交付说明: 2 部署ZK集群 2.1 二进制安装JDK 2.1. ...
- Kubernets二进制安装(4)之Docker安装
注意:需要安装Docker的机器为mfyxw30.mfyxw40.mfyxw50 集群规划 主机名 角色 IP地址 mfyxw30.mfyxw.com Docker 192.168.80.30 mfy ...
- Shpfile文件的字段类型说明
Shpfile文件的字段类型设置如下表所示: 字段类型 字符 字段长度 长整型 N 9 短整型 N 4 浮点型 F 13 双精度 F 19 文本 C 50 特别需要注意的是字段长度,在导出SHP的时候 ...
- python了解未知函数的方法
?func 如图:
- IIS6.0(CVE-2017-7269) 缓冲器溢出
漏洞描述开启WebDAV服务对IIS 6.0存在缓冲区溢出漏洞都可以导致远程代码执行,所以对于目前的IIS 6.0用户而言,可用的变通方案就是关闭WebDAV服务. 漏洞编号CVE-2017-7269 ...