Go程序优化 | 青训营

59 阅读4分钟

优化指南

slice预分配内存

  • 切片的本质是数组分段的描述
    • 包括数组指针
    • 片段长度
    • 片段的容量
  • 切片操作不复制切片指向的元素
  • 创建一个新的切片会复用原来切片的底层数据

但存在大内存释放问题:在已有切片基础上创建切片,不会创建新的底层数组。

——使用copy替代re-slice。

map

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

字符串处理

strings.Builder:字符串拼接,不需要每次拼接重新分配内存

空结构体

不占用任何的内存空间,可作为各种场景下的占位符使用,节省资源

atomic

通过硬件实现,效率比锁高,用于保护一段逻辑,不仅仅是保护一个变量

性能优化工具

原则

  • 依靠数据不是猜测
  • 定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

pprof

pprof知道应用在什么地方消费了多少CPU、内存,用于可视化和性能分析数据工具。

Image.png

运行案例代码,打开浏览器:http://localhost:6060/debug/pprof/

Image.png

CPU

go tool pprof "<http://localhost:6060/debug/pprof/profile?second=10>"

Image.png

top

Image.png

  • flat:当前函数本身的执行耗时
    • == cum,函数中没有用调用其他函数
    • == 0,函数只有其他函数的调用
  • flat%: flat占CPU总时间的比例
  • sum%: 上面每一行的flat%总和
  • cum: 当前函数本身加上其调用函数的总耗时
  • cum%: cum占CPU总时间的比例

list Eat:具体出现什么问题

Image.png

发现是func (t *Tiger) Eat() 消耗的CPU较大,注释该函数代码,CPU性能改进为:

Image.png

内存

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

Image.png

发现是func (m *Mouse) Pee() m.slowBuffer = append(m.slowBuffer, [constant.Mi]byte{})占有大量的内存,注释后内存消耗为:

图片.png

goroutine

go tool pprof -http=:8080 "[http://localhost:6060/debug/pprof/](http://localhost:6060/debug/pprof/heap)goroutine"

图片.png

发现是func (w *Wolf) Drink()time.Sleep(30 * time.Second)消耗大量的资源,注释后资源消耗为: 图片.png

flame graph

Image.png

块的长度表示占用的CPU时间,可交互

在source视图下,搜索wolf

Image.png

go tool pprof -http=:8080 "[http://localhost:6060/debug/pprof/](http://localhost:6060/debug/pprof/heap)mutex"

Image.png

到source视图中看出现问题的代码

Image.png

发现是func (w *Wolf) Howl()m.Unlock()导致没有解锁,注释后结果为:

图片.png

block

go tool pprof -http=:8080 "[http://localhost:6060/debug/pprof/](http://localhost:6060/debug/pprof/mutex)block"

Image.png Image.png

发现是func (c *Cat) Pee()func (w *Wolf) Howl()两个函数导致阻塞。

两个block只展示了一个,使用一些过滤策略将占比小的函数过滤掉

采样过程和原理

CPU

  • 采样对象:函数调用和他们占用的时间

  • 采样率:100次/s,固定

  • 采样时间:从手动启动到手动结束

堆内存

  • 采样程序通过内存分配器在堆上分配和释放内存,记录分配/释放的大小和数量
  • 采样率:每分配512KB记录一次,运行开头修改,1为每次分配均记录
  • 采样时间:程序运行开始到结束

协程

  • goroutine:记录所有用户发起且运行中的goroutine runtime.main 的调用栈信息
  • threadCreate:记录程序创建的所有系统线程的信息

Image.png

block & mutex

阻塞:

  • 采样阻塞操作的次数和耗时
  • 采样率:阻塞耗时超过阈值才会被记录,1为每次均阻塞采样

Image.png

锁竞争

  • 采样争抢锁的次数和耗时
  • 采样率:只记录固定比例的操作,1为每次加锁记录

Image.png

案例

业务服务

概念:

  • 服务:能单独部署,承载一定功能的程序
  • 依赖:A的功能实现依赖B的相应结果,A依赖B
  • 条用链路:支持一个接口请求的行管服务集合及相互之间的依赖
  • 基础库:公共的工具包、中间件

流程

  • 建立服务性能评估手段

    • 服务性能评估方式,不同负载情况下性能表现差异
    • 请求流量构造
    • 压测范围
    • 性能数据采集
  • 分享性能数据,定位性能瓶颈

    • 使用库不规范
    • 高并发场景优化不足
  • 重点优化项改进

    • 正确性是基础
    • 响应数据diff,线上青牛数据录制回放
  • 优化效果验证

    • 关注服务监控
    • 逐步放量
    • 收集性能数据
  • 进一步优化,服务整体链路分析

    • 规范上游服务调用接口,明确场景需求
    • 分析链路,通过业务流程优化提升服务性能

基础库优化

  • 分析瓶颈
  • 内部压测验证
  • 推广业务服务落地验证

Go语言优化

  • 内存分配策略
  • 编译流程更高效
  • 内部压测验证
  • 推广业务落地验证

优点

  • 接入简单,只要调整编译配置
  • 通用性强