Golang基准测试benchmark—测试代码性能

旧城等待, 2022-09-11 04:23 646阅读 0赞

前言

在优化代码或者决定算法选用的时候,性能是很重要的一个指标,比如我最近在做需求的时候需要用哈希算法做签名。一开始想都没想就想用md5,然后IDE上蹦出几个大字:md5有已知的安全问题,建议换成其他算法。然后就考虑换SHA256。问题会不会换一个算法导致性能急剧下降呢?

这时Go语言内置的Benchmark功能就很方便的派上用场了。注意,基准测试受环境影响大,应尽量保证环境稳定,测试时尽量CPU别同时干其他耗性能的事情,别开节能模式。

Go基准测试

Go基准测试是基于Go单元测试的。基准测试放在和单测一样的包下,依旧是xxxx_test.go这样的包下。

函数名约定

与单测函数不同的是,基准测试的函数以Benchmark来做开头,签名像这样:

  1. func BenchmarkXxx(b *testing.B) {
  2. ... }

testing.B

N

基准测试的基本原理是让用户实现一个循环来调用被测算法,外层的函数传入要循环的次数,通过不断的尝试,使得 总耗时/循环次数 趋于稳定后就可以认为这个值就是在这个环境下被测算法的耗时,当然也可以通过指定循环次数等使得提前结束。

而外层函数就是通过 b.N把循环次数传递给我们的,所以在基准测试内应该要有个像这样的循环:

  1. for n := 0; n < b.N; n++ {
  2. // 被测算法
  3. }

ResetTimer()

在开始基准测试前,可能有一些很耗时的准备工作,这个时候就可以通过b.ResetTimer() 重新开始计时

StartTimer()和StopTimer()

如果是在每次循环前后都需要做耗时的准备工作,则可以使用b.StartTimer()b.StopTimer()的组合,来忽略那些准备阶段的时间。

其他方法

testing.b也提供正常的测试里提供的那些函数,如Fail使得测试失败,Run使得运行子测试,Helper来标记辅助函数。

指令

运行单测的 go test指令默认是会忽略基准测试的。
为了让go test能够运行基准测试,我们需要直接指定-bench标签,其参数是正则表达式。如果要运行当前包下所有的基准测试:

  1. go test -bench=.

这样就会运行当前路径下所有的基准测试。

如果要运行所有名字包含go或lang的测试:

  1. go test -bench="go|lang"

另外,由于默认会运行单测,为了不让单测的输出影响输出结果,可以故意指定个不存在的单测函数名的:

  1. go test -bench="go|lang" -run=noExist

还有一些其他的相关指令:






























option 描述
-benchmem 性能测试的时候显示测试函数的内存分配的统计信息,等价于在基准测试中调用b.ReportAllocs()
-count n 运行多少次,默认1次
-timeout t 超时时间,超过会panic,默认10分钟
-cpu 指定GOMAXPROCS,可以通过,传入一个列表
-benchtime 指定执行时间(e.g. 2s)或具体次数(e.g. 10x)

示例

继续我们的故事,我现在需要测测用md5和SHA256生成签名的效率差异,实际使用中是给一个uuid生成签名。于是基准测试就写成了这样:

util_test.go:

  1. package util
  2. import (
  3. "crypto/md5"
  4. "crypto/sha256"
  5. "github.com/google/uuid"
  6. "testing"
  7. )
  8. func BenchmarkSha256(b *testing.B) {
  9. target := []byte(uuid.New().String())
  10. b.ResetTimer()
  11. for n := 0; n < b.N; n++ {
  12. sha256.Sum256(target)
  13. }
  14. }
  15. func BenchmarkMd5(b *testing.B) {
  16. target := []byte(uuid.New().String())
  17. b.ResetTimer()
  18. for n := 0; n < b.N; n++ {
  19. md5.Sum(target)
  20. }
  21. }

输出

运行测试:

  1. admin@……:util$ go test -bench=. -run=none
  2. goos: darwin
  3. goarch: amd64
  4. pkg: ……/util
  5. BenchmarkSha256-12 6331168 184 ns/op
  6. BenchmarkMd5-12 11321952 103 ns/op
  7. PASS
  8. ok ……/util 4.392s

看到报告里头函数后面的-12了吗?这个表示运行时对应的 GOMAXPROCS 的值。接着的 6331168 表示最后一次给的N值,也就是认为结果可信的那次的循环的次数,最后的 184 ns/op表示每次循环需要花费 184 纳秒。

我们可以看出,SHA256在uuid字符串的场景下,计算费时差不多是Md5的1.8倍,还算可以接受。

可以多用上几个选项来看看

  1. admin@……:util$ go test -bench=. -run=none -count=3 -cpu=2,4 -benchmem
  2. goos: darwin
  3. goarch: amd64
  4. pkg: ……/util
  5. BenchmarkSha256-2 6375644 178 ns/op 0 B/op 0 allocs/op
  6. BenchmarkSha256-2 6575397 180 ns/op 0 B/op 0 allocs/op
  7. BenchmarkSha256-2 6646250 182 ns/op 0 B/op 0 allocs/op
  8. BenchmarkSha256-4 6566167 183 ns/op 0 B/op 0 allocs/op
  9. BenchmarkSha256-4 6476132 190 ns/op 0 B/op 0 allocs/op
  10. BenchmarkSha256-4 6327001 192 ns/op 0 B/op 0 allocs/op
  11. BenchmarkMd5-2 10067620 107 ns/op 0 B/op 0 allocs/op
  12. BenchmarkMd5-2 11456790 104 ns/op 0 B/op 0 allocs/op
  13. BenchmarkMd5-2 11314701 106 ns/op 0 B/op 0 allocs/op
  14. BenchmarkMd5-4 10312569 105 ns/op 0 B/op 0 allocs/op
  15. BenchmarkMd5-4 10565292 102 ns/op 0 B/op 0 allocs/op
  16. BenchmarkMd5-4 11695822 103 ns/op 0 B/op 0 allocs/op
  17. PASS
  18. ok ……/util 17.036s

上面的测试中,我们指定了要运行3趟测试,分别用2核和4核来测试,并输出了内存的数据。可以看出,几次测试有波动,核数增加对运行速度并没什么软用,应为这两个算法都是串行调用。而且这两算法都不需要分配内存。

结语

当你没法确定你的优化到底有没效果时,不懂到时候绩效咋写时,不妨试试用基准测试测下性能吧!

发表评论

表情:
评论列表 (有 0 条评论,646人围观)

还没有评论,来说两句吧...

相关阅读

    相关 性能基准测试基本流程

    性能基准测试的基本流程如下: 1. 确定测试目标和指标: 确定测试的目标,例如报表加载时间、数据刷新速度、并发用户数等性能指标。 2. 选择测试环境:配置一个与实际生产环境

    相关 Go基准测试Benchmark

    > Go语言自带了一个强大的测试框架,其中包括基准测试(Benchmark)功能,基准测试用于测量和评估一段代码的性能。 我们可以通过在Go的测试文件中编写特殊格式的函数来创

    相关 Golang-代码测试

    介绍 单元测试通常是由软件开发⼈员编写和运⾏的⾃动测试,以确保应⽤程序的某个部分(称为“单元”)符合其设计并按预期运⾏。在过程式编程中,⼀个单元可以是⼀个完整的模块,但它