补课第3节笔记:性能优化指南|青训营

65 阅读5分钟

class 3-2:性能优化指南

时间效率 vs 空间效率

评估代码性能:基准性能测试Benchmark

  • go test -bench=. -benchmem

  • go test -run=. -v

  • slice预分配内存空间

  • 使用copy代替re-slice,降低内存使用

  • map预分配内存,减少内存拷贝和rehash消耗

性能优化 - 字符串处理

+拼接/strings.Builder √/byte.Buffer拼接字符串

分析:

  • 字符串在Go语言是不可变类型,占用内存大小固定;
  • 使用+每次都需要重新分配内存;
  • strings.Builder 和byte.Buffer底层都是[]byte数组;
  • 内存扩容策略,不需要每次拼接都重新分配内存

byte.Buffer转化为字符串时重新申请了一块空间

strings.Builder直接将底层的[]byte转换成字符串类型返回

性能优化 - 空结构体

空结构体struct{}实例不占据任何内存空间 可作为各种场景下的占位符使用

  • 节省资源
  • 空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符

实现Set,可以考虑用map实现

性能优化 - atomic包

多线程问题:通过加锁的方式保证每次更新都是准确的,不会被其他变更影响,操作系统实现,为系统调用,时间更长

atomic包:通过硬件实现,效率高;对于非数值操作,可以使用atomic.Value,能承载一个interface{}

保证正确可靠、简介清晰的质量要求下提供程序性能

class 3-3:性能调优实战

性能调优原则:

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

性能分析工具:pproof github.com/wolforge/go…

cpu

  • 采集10s数据

命令:go tool pproof "http://localhost:6060/debug/proof/profile?seconds=10"

  • 展示结果,查看占用资源最多的函数

命令:top

flat 当前函数本身执行耗时

flat% flat占CPU总时间的比例

sum% flat%的总和

cum 当前函数本身加其调用函数的总耗时

cum% cum占CPU总时间的比例

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

命令:list

  • 调用关系可视化

命令:web

heap - 堆内存

命令:go tool pproof -http=:8080 "http://localhost:6060/debug/proof/heap"

-http=:8080 表示可视化视图

top视图/source视图,对应命令行的top/list

火焰图:从上到下为函数的调用顺序

火焰图解释:www.infoq.cn/article/a8k…

  • alloc_objects: 程序累计申请的对象数

  • inuse_objects: 程序当前持有的对象数

  • alloc_space: 程序累计申请的内存大小

  • inuse_space: 程序当前占用的内存大小

goroutine - 协程

命令:go tool pproof -http=:8080 "http://localhost:6060/debug/proof/goroutine"

可视化视图:火焰图(动态图,支持点击块分析)

命令:go tool pproof -http=:8080 "http://localhost:6060/debug/proof/mutex"

阻塞

命令:go tool pproof -http=:8080 "http://localhost:6060/debug/proof/block"

注意:有时候会过滤掉部分阻塞

class 3-4:pproof采样过程及原理

cpu

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

  • 采样率:100次/秒,固定值

  • 采样时间:手动启动/手动结束

开始采样 - 设定信号处理函数 - 开启定时器

停止采样 - 取消信号处理函数 - 关闭定时器

heap - 堆内存

采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量

采样率:每分配512kb记录一次,科长运行开头修改

采样时间:从程序运行开始到采样时

计算方式:inuse = alloc(已分配内存) - free(空闲内存)

Goroutine-协程/ThreadCreate-线程

Goroutine:记录所有用户发起且在运行中的Goroutine的runtime.main的调用栈信息

ThreadCreate:记录程序创建的所有系统线程的信息

Goroutine:stop the world - 遍历allg切片 - 输出创建g的堆栈 - start the world

ThreadCreate:stop the world - 遍历allg链表 - 输出创建m的堆栈 - start the world

阻塞 & 锁

阻塞是超过某一阈值才会被记录,而锁只记录固定比例的锁操作

性能调优案例

  • 业务服务优化
  • 基础库优化
  • Go语言优化

业务服务优化

名词解释:

  • 服务:能单独部署,承载一定功能的程序
  • 依赖
  • 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
  • 基础库:公共的工具包、中间件

单个服务流程:

  • 建立服务性能评估手段(请求流量构造、不同负载、单机/集群压测范围、单机/集群性能数据采集)
  • 分析性能数据,定位性能瓶颈(使用库不规范/日志库使用不规范/离并发场景优化不足)
  • 优化(先正确,再优化。 相应数据diff,线上请求数据录制回放,新旧逻辑接口数据diff)
  • 优化效果验证(重复压测验证、上线评估:关注服务监控、逐步放量、收集性能数据)

服务链路分析与优化:

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

链路分析思路:

  • service调用时,是否更小的数据集就能满足需求
  • service A是否实时需要service B数据,是否可以通过加缓存减轻服务调用压力,加速相应
  • 由于A逻辑复杂/多人维护等,可能导致重复调用B服务,是否可以合并两个请求

基础库优化:AB实验SDK优化

  • 分析基础库核心逻辑和性能瓶颈(设计改善方案,数据按需获取,数据序列化协议优化)
  • 内部压测验证
  • 推广业务服务落地验证

Go语言优化:编译器&运行时优化

  • 优化内存分配策略
  • 优化代码编译流程,生成更高效的程序
  • 内部压测验证
  • 推广业务服务,落地验证

优点:接入简单,只需要调整编译配置,通用性强