Go语言之基准测试

简介: 什么是基准测试基准测试,是一种测试代码性能的方法,比如你有多种不同的方案,都可以解决问题,那么到底是那种方案性能更好呢?这时候基准测试就派上用场了。基准测试主要是通过测试CPU和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。
什么是基准测试


基准测试,是一种测试代码性能的方法,比如你有多种不同的方案,都可以解决问题,那么到底是那种方案性能更好呢?这时候基准测试就派上用场了。


基准测试主要是通过测试CPU和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。比如链接池的数量不是越多越好,那么哪个值才是最优值呢,这就需要配合基准测试不断调优了。


如何编写基准测试


基准测试代码的编写和单元测试非常相似,它也有一定的规则,我们先看一个示例。


itoa_test.go


func BenchmarkSprintf(b *testing.B){
    num:=10
    b.ResetTimer()
    for i:=0;i<b.N;i++{
        fmt.Sprintf("%d",num)
    }
}


这是一个基准测试的例子,从中我们可以看出以下规则:


  • 基准测试的代码文件必须以_test.go结尾。


  • 基准测试的函数必须以Benchmark开头,必须是可导出的。


  • 基准测试函数必须接受一个指向Benchmark类型的指针作为唯一参数。


  • 基准测试函数不能有返回值。


  • b.ResetTimer是重置计时器,这样可以避免for循环之前的初始化代码的干扰。


  • 最后的for循环很重要,被测试的代码要放到循环里。


  • b.N是基准测试框架提供的,表示循环的次数,因为需要反复调用测试的代码,才可以评估性能。


下面我们运行下基准测试,看看效果。


  hello go test -bench=. -run=none
BenchmarkSprintf-8      20000000               117 ns/op PASS ok      flysnow.org/hello       2.474s


运行基准测试也要使用go test命令,不过我们要加上-bench=标记,它接受一个表达式作为参数,匹配基准测试的函数,.表示运行所有基准测试。


因为默认情况下go test会运行单元测试,为了防止单元测试的输出影响我们查看基准测试的结果,可以使用-run=匹配一个从来没有的单元测试方法,过滤掉单元测试的输出,我们这里使用none,因为我们基本上不会创建这个名字的单元测试方法。


下面着重解释下说出的结果,看到函数后面的-8了吗?这个表示运行时对应的GOMAXPROCS的值。接着的20000000表示运行for循环的次数,也就是调用被测试代码的次数,最后的117 ns/op表示每次需要话费 117 纳秒。


以上是测试时间默认是 1 秒,也就是 1 秒的时间,调用两千万次,每次调用花费 117 纳秒。如果想让测试运行的时间更长,可以通过-benchtime指定,比如 3 秒。


  hello go test -bench=. -benchtime=3s -run=none
BenchmarkSprintf-8      50000000               109 ns/op PASS ok      flysnow.org/hello       5.628s


可以发现,我们加长了测试时间,测试的次数变多了,但是最终的性能结果:每次执行的时间,并没有太大变化。一般来说这个值最好不要超过3秒,意义不大。


性能对比


上面那个基准测试的例子,其实是一个int类型转为string类型的例子,标准库里还有几种方法,我们看下哪种性能更加。


func BenchmarkSprintf(b *testing.B){
    num:=10
    b.ResetTimer()
    for i:=0;i<b.N;i++{
        fmt.Sprintf("%d",num)
    }}func BenchmarkFormat(b *testing.B){
    num:=int64(10)
    b.ResetTimer()
    for i:=0;i<b.N;i++{
        strconv.FormatInt(num,10)
    }}func BenchmarkItoa(b *testing.B){
    num:=10
    b.ResetTimer()    
    for i:=0;i<b.N;i++{
        strconv.Itoa(num)
    }
}


运行基准测试,看看结果:


  hello go test -bench=. -run=none              BenchmarkSprintf-8      20000000               117 ns/op
BenchmarkFormat-8       50000000                33.3 ns/op
BenchmarkItoa-8         50000000                34.9 ns/op PASS ok      flysnow.org/hello       5.951s


从结果上看strconv.FormatInt函数是最快的,其次是strconv.Itoa,然后是fmt.Sprintf最慢,前两个函数性能达到了最后一个的 3 倍多。那么最后一个为什么这么慢的,我们再通过-benchmem找到根本原因。


  hello go test -bench=. -benchmem -run=none
BenchmarkSprintf-8      20000000       110 ns/op      16 B/op      2 allocs/op
BenchmarkFormat-8       50000000       31.0 ns/op     2 B/op       1 allocs/op
BenchmarkItoa-8         50000000       33.1 ns/op     2 B/op       1 allocs/op PASS ok      flysnow.org/hello       5.610s


-benchmem可以提供每次操作分配内存的次数,以及每次操作分配的字节数。从结果我们可以看到,性能高的两个函数,每次操作都是进行 1 次内存分配,而最慢的那个要分配 2 次;性能高的每次操作分配 2 个字节内存,而慢的那个函数每次需要分配 16 字节的内存。从这个数据我们就知道它为什么这么慢了,内存分配都占用都太高。


在代码开发中,对于我们要求性能的地方,编写基准测试非常重要,这有助于我们开发出性能更好的代码。不过性能、可用性、复用性等也要有一个相对的取舍,不能为了追求性能而过度优化。


目录
相关文章
|
7天前
|
Go
Go 语言循环语句
在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。
17 1
|
6天前
|
Go 开发者
探索Go语言的并发之美
在Go语言的世界里,"并发"不仅仅是一个特性,它是一种哲学。本文将带你领略Go语言中goroutine和channel的魔力,揭示如何通过Go的并发机制来构建高效、可靠的系统。我们将通过一个简单的示例,展示如何利用Go的并发特性来解决实际问题,让你的程序像Go一样,轻盈而强大。
|
7天前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
7天前
|
Go
go语言创建字典
go语言创建字典
|
8天前
|
安全 Go 数据处理
探索Go语言的并发之美:Goroutines与Channels
在Go语言的世界里,"并发"不仅仅是一个概念,它是一种生活的方式。本文将带你领略Go语言中Goroutines和Channels的魔力,它们是如何让并发编程变得既简单又高效。我们将通过一个简单的示例,展示如何使用这些工具来构建一个高性能的网络服务。
|
8天前
|
关系型数据库 Go 数据处理
高效数据迁移:使用Go语言优化ETL流程
在本文中,我们将探索Go语言在处理大规模数据迁移任务中的独特优势,以及如何通过Go语言的并发特性来优化数据提取、转换和加载(ETL)流程。不同于其他摘要,本文不仅展示了Go语言在ETL过程中的应用,还提供了实用的代码示例和性能对比分析。
|
9天前
|
Go 定位技术 索引
Go 语言Map(集合) | 19
Go 语言Map(集合) | 19
|
9天前
|
Go
go语言注释,标识符 | 17
go语言注释,标识符 | 17
|
8天前
|
NoSQL Go API
go语言操作Redis
go语言操作Redis
|
8天前
|
Unix Go
go语言获取当前时间戳
go语言获取当前时间戳
下一篇
无影云桌面