go 后端笔记6 性能优化

106 阅读4分钟

性能优化前提:正确可靠,简洁清晰

性能调优原则:

  • 依靠性能而非猜测
  • 定位最大瓶颈而非细枝末节
  • 不要过早优化,预期将要出现性能问题时再优化
  • 不要过度优化,避免后期功能修改不兼容

常见方法

Slice

  • 尽量在 make() 初始化时提供足够的内存容量信息
    • 减少内存分配和复制:切片本质是一个数组片段,容量不足时会创建一个新数组并复制原有元素(或复用底层数组)
  • 使用 copy 代替切片的切片
    • 切片的切片复用底层数组,即使原切片不再使用也不会释放,容易造成大内存未释放问题

map

  • 尽量预分配足够空间,减少 map 扩容和 rehash

string

  • 大量字符串拼接使用 strings.Builder,底层使用 []byte
    • 尽量通过 Grow 预分配长度

struct

  • 使用空结构体 struct{} 作为占位符,空结构体本身不占据任何内存空间,可用于实现 setmap[T]struct{}

多线程

  • 使用 atomic 原子类代替锁,通过硬件实现,效率更高
  • sync.Mutex 用意是保护一段逻辑,而不是保护一个变量

pprof

可视化性能分析工具,包括工具、采样、分析、展示等模块

Pasted image 20241114231513.png

[!note] 火焰图:自上而下表示调用顺序,横向宽度表示耗时

引入 net/http/pprofnet/http 模块后,在代码中开启服务器,这里在 6060 端口开启,可通过 localhost:6060/debug/pprof/ 访问

Pasted image 20241114233003.png

性能排查

go tool pprof -http=:端口 "数据地址",其中数据地址即前面 6060 的具体页项,依赖于 graphviz(注意添加环境变量)

如将堆内存的分析报告映射到 8080 端口,使用

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

控制台访问 go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" 采集 10s 内 CPU 使用情况

Pasted image 20241114233219.png

  • toptopN:查看占用资源最多的函数
    • flatflat%:当前函数本身占用
    • sum%:当前行与前面行的 flat% 总和
    • cumcum%:当前函数与函数调用的其他函数总和

Pasted image 20241114233443.png

  • list 函数名:进入函数,查看占用最大的部分

Pasted image 20241114233735.png

  • web:创建可视化调用关系图,需要依赖 Graphviz 工具(实际生成一个 svg 图)

采样过程及原理

  • CPU:使用系统定时信号采样(SIGPROF 信号)
    • 采样对象:函数调用及其占用时间
    • 采样率:100 次/秒,固定
    • 采样时间:从调用开始到采样结束
  • Heap 堆内存:通过内存分配器记录分配、释放大小和数量,因此记录不到栈内存
    • 采样率:默认 512K 记录一次
    • 采样时间:从程序运行开始到采样时
    • 采样指标:alloc_spacealloc_objectsinuse_spaceinuse_objects,其中 inuse = alloc - free
  • Goroutine 协程:Stop the world - 遍历 allg 切片 - 输出 g 堆栈 - Start the world
    • 记录用户发起且在运行中的(即非 runtime 的)调用堆栈
    • 记录 runtime.main 的调用堆栈
  • ThreadCreate 线程:Stop the world - 遍历 allm 链表 - 输出 m 堆栈 - Start the world
    • 记录程序创建的所有系统线程信息
  • Block 阻塞、Mutex 锁竞争:阻塞(或锁竞争) - 上报 Profile 调用栈和耗时 - 采样 - 遍历记录 - 统计次数和超时
    • 采样:操作的次数和超时
    • 采样率:
      • 阻塞记录耗时超过阈值的操作
      • 采样率记录固定比例的锁操作

调优实战

业务服务优化

服务:能单独部署,承载一定功能的程序

调用链路:支持一个接口请求的相关服务集合及依赖关系

基础库:公共工具包,中间件等

  1. 建立服务性能评估手段
  2. 分析性能数据,定位性能瓶颈
    • 调用库不规范
    • 日志
    • 对比高峰与低峰数据
  3. 重点优化项改造
    • 正确性判断:比较优化前后的输出
  4. 优化效果验证
  5. 进一步优化:规范上游服务调用,分析链路

基础库优化

  1. 分析核心逻辑和性能瓶颈
  2. 内部压测验证
  3. 推广业务服务落地验证

Go 语言优化

接入简单(调整编译配置),通用性强

  • 优化内存分配策略
  • 优化编译流程
  • 内部压测验证
  • 推广业务服务落地验证