Go 性能优化笔记 | 青训营

83 阅读3分钟

1. 高质量编程

高质量编程:简单性、可读性、生产力

  • 边界条件考虑完备
  • 异常处理,稳定性保证
  • 易读易维护

编码规范

  • 代码格式:gofmt,goimports (gofmt + 依赖管理)

  • 注释

    • 解释代码作用
    • 解释代码怎么做的
    • 解释代码实现原因,提供额外的上下文
    • 解释代码什么情况会出错
    • 公共符号要有注释
  • 命名规范

    • 变量:缩略词全大写,但位于变量开头且不需导出时,全小写
    • package:只有小写字母
  • 错误和异常处理

    • 简单错误:errors.New("xxx")
    • 错误的 Warp 和 Unwarp: Warp 提供了一个 error 嵌套另一个 error 的能力,从而生成 error 的跟踪链
    • fmt.Errorf("xxx : %w", err) 将 err 关联到错误链中
    • 错误判定:如 errors.Is(err, fs.ERrrNotExist)
    • 在错误链上获取特定种类的错误:errors.As(r, &pathError)
    • recover() :只能在被 defer 的函数中使用,嵌套无法生效,只在当前 goroutine 生效
    • 多个 defer 语句后进先出

2. 性能优化

go 语言的基准性能测试 benchmark 工具 : go test -bench=. -benchmem

性能优化建议:

  • Slice 预分配内存:在 make() 初始化时提供容量信息

    • 如:data := make([]int, 0, size)
    • 因为切片是一个结构体,包括数组指针、片段长度、片段容量(不改变内容分配下的最大长度);切片操作不复制切片指向的元素,创建新的切片会复用原来切片的底层数组
    • 在原有切片上创建新切片,有内存未释放的问题,建议先创建小切片,然后用 copy()
  • map 预分配内存

    • 如:data := make(map[int]int, size)
    • 提前分配好空间可以减少内存拷贝和 rehash 的消耗
  • 字符串处理

    • 字符串拼接:使用 strings.Builder

      • var builder strings.Builder
      • builder.WriteString(str)
      • return builder.String()
    • 或者使用 buffer

      • buf := new(bytes.Buffer)
      • buf.Grow(n * len(str))
      • buf.WriteString(str)
      • return buf.String()
    • 字符串是不可变类型,占用内存大小固定

    • "+" 每次都会重新分配内存

    • strings.Builder , bytes.Buffer 底层是 []byte 数组,内存扩容策略,不需要每次拼接重新分配内存

    • strings.Builder 可以预分配内存:builder.Grow(n * len(str))

  • 使用空结构体节省内存

    • 空 struct{} 实例不占用内存空间
  • 多线程编程:使用 atomic 包 比 加锁的性能好

    • 锁的实现是通过操作系统,属于系统调用
    • atomic 通过硬件实现

3. 性能调优实战

性能分析工具 pprof

golang pprof 实战 | Wolfogre's Blog

  • 下载代码到本地,go env -w GO111MODULE=on ,go.mod 中的 module 改为 module go-pprof-practice-master,然后其他文件中 import 的路径改成 go-pprof-practice-master/xxx

  • 运行 main.go

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

topN : 查看占用资源最多的函数

  • Tiger.Eat

  • flat : 当前函数本身的指向耗时

  • flat% : flat 占 CPU 总时间的比例

  • sum% : 上面每一行的 flat% 的总和

  • cum : 当前函数本身 + 其调用函数的总耗时

  • cum% :cum 占 CPU 总时间的比例

  • 什么情况下 flat == cum , flat == 0?

    • flat == cum:没有调用其他函数
    • flat == 0: 函数中只有其他函数的调用

list : 根据指定的正则表达式查找代码行

  • for 循环

web:调用关系可视化

  • 下载 graphviz,配置环境变量

heap - 堆内存

  • 可视化:在命令中加 -http(需要 graphviz)
go tool pprof -http localhost:8080 "http://localhost:6060/debug/pprof/heap"
  • Graph 视图

  • Source 视图

  • SAMPLE 里面有4个统计内存开销的方式

    • alloc_objects: 程序累计申请的对象数
    • alloc_space: 程序累计申请的内存大小
    • inuse_objects: 程序当前持有的对象数
    • inuse_space: 程序当前占用的内存大小

goroutine 泄漏 —— 导致内存泄漏

go tool pprof -http localhost:8080 "http://localhost:6060/debug/pprof/goroutine"
  • view 下切换为 flame graph (火焰图)

    • 从上到下是调用顺序
    • 每一块表示一个函数,越长表示占用 CPU 时间更长 (wolf.Drink)
  • 然后切换到 source 视图,搜索 wolf

mutex 锁

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

block 阻塞

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