Go语言程序性能优化入门 | 青训营笔记

59 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

主要是学习影响性能的因素和接触工具(之前没认真学过),所以写的东西非常入门

性能优化建议

简介

  • 性能优化的前提是满足正确可靠、简洁清晰等质量因素
  • 性能优化是综合评估,有时候时间效率和空间效率可能对立

Benchmark

  • 性能的表现要用数据衡量

  • go test -bench=. benchmem

  • 结果说明:

    2023-01-19-19-24-39-image.png

Slice

Slice 预分配内存

  • 尽可能在使用 make() 初始化切片时提供容量信息

    2023-01-19-20-27-02-image.png

大内存未释放

在已有切片的基础上创建切片,不会更新底层数组

可以使用 copy 代替 re-slice

//re-slice
func getLastBySlice(origin []int) []int {
    return origin[len(origin) - 2:]
}

// copy
func getLastByCopy(origin []int ) []int {
    result := make([]int, 2)
    copy(result, origin[len(origin) - 2:])
    return result
}

Map

  • 不断向 map 中添加元素的操作会触发 map 的扩容,影响性能
  • 可以提前分配空间,以减少内存拷贝和 Rehash 的消耗
  • 建议根据实际需求提前预估需要的空间

String

String 的拼接是有讲究的

建议使用 strings.Builder

  • 使用 + 拼接的性能是最差的,strings.Builder, bytes.Buffer 相近,而 strings.Builder 更快

  • 分析

    • 字符串在 Go 语言中是不可变类型,故其占用的内存大小是固定的

      • 每次使用 + 都会重新分配内存
    • strings.Builder 和 bytes.Builder 底层都是 []byte 数组

      • 由于其内存扩容策略,不需要每次拼接都重新分配内存

空结构体

  • 空结构体示例不占用任何的内存空间
  • 可以作为各种场景下的占位符使用

Atomic 包

在多线程场景中可以维护一个变量:

  • 锁的实现是通过操作系统来实现的,属于系统调用
  • atomic 操作是通过硬件实现的,效率比锁高
  • sync.Mutex 应该用于保护一段逻辑,而不仅仅用于保护一个变量
  • 对于非数值操作,可以使用 atomic.Value,其能承载一个interface{}

小结

  • 避免常见的性能陷阱
  • 普通应用代码不要一味追求性能
  • 越高级的优化手段越容易出现问题
  • 在满足正确可靠、简洁清晰的质量要求的前体现提高程序的性能

疑问

  • Atomic包是通过硬件实现的,而Go语言是一个跨平台的语言,它是怎么保证各个平台都能够成功运行的

性能优化分析工具

说在前面

  • 性能优化要依据数据
  • 要定位最大的瓶颈
  • 不要过早优化:当产品迭代时,代码可能会被优化
  • 不要过度优化:可能出现无法兼容的问题

性能分析工具pprof

说明

  • 可以获取什么地方消耗了多少 CPU、Memory
  • 可视化

功能简介

2023-01-19-20-50-37-image.png

GUI

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

如果出现 Could not execute dot; may need to install graphviz.报错,先前往[graphviz的官网](Download | Graphviz)下载并安装 graphviz

Source 视图

搜索相关问题函数

top指令

对于 CPU 的分析执行 top 指令后会返回如下几个参数:

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

Heap

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

Goroutine

火焰图:

  • 由上至下表示调用顺序
  • 每一个块表示一个函数,块越长表示占用CPU时间越长
  • 火焰图是动态的

其他问题

  • Mutex (锁)
  • Block (阻塞)
  • ……