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语言优化:编译器&运行时优化
- 优化内存分配策略
- 优化代码编译流程,生成更高效的程序
- 内部压测验证
- 推广业务服务,落地验证
优点:接入简单,只需要调整编译配置,通用性强