Go语言性能调优实战案例-工程实践 | 青训营笔记

152 阅读4分钟

Go语言性能调优实战案例-工程实践 | 青训营笔记

参加 [第六届青训营] 笔记创作第五篇

性能调优课的内容

分为3个部分:

  • 第一个部分是对性能调优常见的方面进行简介讲解
  • 第二个部分介绍了Go语言用于性能分析的工具pprof
  • 第三个部分是使用pprof来进行实战调优的实例

Go语言性能调优

性能评估简介

Go语言提供了基准性能测试的 benchmark 工具,Benchmark是Go语言中用于性能测试和比较的工具。它可以帮助我们评估代码的执行速度和资源消耗,并提供详细的结果报告。使用代码如下:

go test -bench=. -benchmen
  1. 在测试文件中,定义一个以Benchmark开头的函数,函数签名为func BenchmarkXxx(b *testing.B),其中Xxx是被测试的函数名。

  2. 在Benchmark函数中,使用b.N来获取测试的迭代次数,然后编写需要测试的代码。

性能优化之slice

slice方面的性能优化主要是对内存的预分配以及管理内存的释放。

slice结构如下:

type slice struct {
    array unsafe.Pointer  // 底层数组的指针
    len int               // 长度
    cap int               // 容量
}

当切片中元素个数大于切片大小时会发生扩容操作。扩容操作花费一定时间,浪费性能,为了节省这部分性能开销,如果知道具体容量,就应该预先设置容量大小。

切片中在创建小的切片,小的切片不会复制大切片的底层数组,而是选择公用一个底层数组,使用新的指针指向合适的位置。但是当这样一个场景,已经存在大的切片 a := [1:10000]int,这时候使用 a[1:3] 创建一个小的切片,这个切片会引用大的切片的底层数组(该数组的大小10000),进行垃圾回收的时候,会分析引用关系,因为小的切片这2个int的空间,10000int的数组空间被引用着,不能释放。这就造成系统资源的极大浪费。

这个问题可以通过使用copy代替re-slice来得到解决。

性能优化之Map

与slice相似,map也有着合理预分配空间可以优化性能的特性。因为不断往Map中put元素,会引发扩容,如果提前分配,可以减少rehash、分配内存的开销。

可以使用make函数在创建map的时候就指定初始容量。 对所需要用到的Map最大容量进行大致预估,并将其作为参数传递给make函数。

func createMap() {
    m := make(map[int]string)
    for i := 0; i < 10000; i++ {
        m[i] = fmt.Sprintf("value%d", i)
    }
}

Benchmark测试:

func BenchmarkCreateMap(b *testing.B) {
    for i := 0; i < b.N; i++ {
        createMap()
    }
}

pprof工具使用实战

性能调优原则

  1. 依靠数据而非猜测
  2. 定位最大瓶颈而非细枝末节
  3. 不需要过早的进行优化
  4. 考虑优化成本,不需要过度

配置

使用pprof需要在程序中导入net/http/pprof包,并在代码中添加相应的路由处理函数。例如,可以在主函数中添加以下代码来启用pprof:

import _ "net/http/pprof"

func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
}

terminal启动了项目源码后,再开一个terminal并输入以下采样代码执行

go tool pprof "http://127.0.0.1:6060/debug/pprof/profile?seconds=10"

执行后使用top命令查看cpu性能分析报告可以看到:

image.png

参数解释:

  • flat 当前函数的本身的执行耗时
  • flat% flat 占 cpu 总时间的比例
  • sum% 上面每一行的 flat% 综合
  • cum 指当前函数本身加上其调用函数的总耗时
  • cum% cum 占 CPU 总时间的比例

当flat == cum时,根据定义可知当前函数并没有调用其他函数。 相对的,当flat == 0时,那么函数中只有其他函数的调用。

由top看到了eat函数占用cpu较多,故这里用list指定罗列出eat函数的源代码:

image.png

可以使用web命令进行网页可视化显示,例如如果要查看内存的可视化情况,可以输入以下代码:

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"

image.png

要看cpu的情况其实把最后的heap改为cpu即可

其他要看协程之类的其实也一样,改为block,goroutine等等,分析思路也是一样的:

  1. 流程图里分析找到占用最大资源的函数
  2. 通过source找到具体行
  3. 注释掉具体行后运行。