1 性能优化
前提满足正确可靠、简洁清晰等质量因素。
所以针对Go语言特性,介绍Go相关的性能优化建议。
1.1 Benchmark
性能表现需要实际数据衡量。
Go语言提供了支持基准性能测试的benchmark工具
测试代码:
终端输入命令: go test -bench=. -benchmem fib_test.go
结果:
第一列:BenchmarkFib10是测试函数名,-8表示GOMAXPROCS(核数)得值为16
第二列:表示一共执行3986155次即b.N的值,
第三列:每次执行花费326ns
第四列:每次申请多大的内存
第五列:每次申请几次内存
1.2 Slice
slice预分配内存
尽可能使用make()初始化切片时提供容量信息
指定后它的内存等分配效率越高
原因
切片本质上是一个数组片段的描述:
包括数组指针
片段的长度
片段的容量(不改变内存分配情况下的最大长度)
切片操作并不复制切片指向的元素
创建一个新的切片会复用原来切片的底层数组
type slice struct {
array unsafe.Pointer
len int
cap int
}
另一个陷阱:大内存未释放
在已有切片的基础上创建切片,不会创建新的底层数组
场景:
原切片较大,代码在原切片基础上新建小切片
原底层数组在内存中有引用,得不到释放
可使用copy代替re-slice
1.3 map
map预内存分配
分析:
不断向map中添加元素的操作会触发map的扩容
提前分配好空间可以减少内存拷贝和Rehash的消耗
建议根据实际需求提前预估好需要的空间
1.4 字符串处理
使用strings.Builder
1
2
3
使用+拼接性能最差,strings.Builder,bytes.Buffer相近,strings.Buffer更快。
分析:
字符串在Go语言中是不可变类型,占用内存大小是固定的
使用+每次都会重新分配内存
strings.Builder,bytes.Buffer底层都是[]byte 数组
内存扩容策略,不需要每次拼接重新分配内存
1.5 空结构体
使用空结构体节省内存
空结构体struct{} 实例不占据任何的内存空间
可作为各种场景下的占位符使用
节省资源
空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符
1.6 atomic包
锁的实现是通过操作系统来实现,属于系统调用
atomic操作是通过硬件实现,效率比锁高
sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
对于非数值操作,可以使用atomic.Value,能承载一个interface{}
2 性能调优实战
性能调优原则:
要求依靠数据不是猜测
要定位最大瓶颈而不是细枝末节
不要过早优化
不要过度优化
2.1 性能分析工具pprof
说明
希望知道应用在什么地方耗费了多少CPU、Menory
pprof是用于可视化和分析性能分析数据的工具
2.1.1 功能简介
2.1.2 排查实战
搭建pprof实践项目
GitHub(来自Wolfogre)
项目提前埋下了一些炸弹代码,产生可观测的性能问题
前置准备:
下载项目代码,能够编译运行
会占用1CPU核心和超过1GB的内存
对于前置准备的代码:
CPU
go tool pprof "http://127.0.0.1:6060/debug/pprof/profile?seconds=10"
top
命令:topN
查看占用资源最多的函数
命令:list
根据制定的正则表达式查找代码行
命令:web
调用关系可视化
Heap-堆内存
go tool pprof -http=:8080 "http://192.168.28.1:139/debug/pprof/heap"
2.1.3 goroutine-协程
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
由上到下表示调用顺序
每一块代表一个函数,越长代表占用CPU的事件更长
火焰图是动态的,支持点击块进行分析
支持搜索,在Source视图下搜索wolf
2.1.4 锁
mutex-锁
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex
2.1.5 block-阻塞
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block
两个Block为什么只展示了一个
第二个阻塞操作是什么
2.2 pprof采样过程和原理
CPU
采样对象:函数调用和它们占用的时间
采样率:100次/秒,固定值
采样时间:从手动启动到手动结束
开始采样————>设定信号处理函数————>开启定时器
停止采样————>取消信号处理函数————>关闭定时器
详细过程
操作系统:每10ms向进程发送一次SIGPROF信号
进程:每次接收到SIGPROF会记录调用堆栈
写缓冲:每100ms读取已经记录的调用栈并写入输出流
2.2.1 Heap-堆内存
采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
采样率:每分配512KB记录一次,可在运行开头修改,1为每次分配均记录
采样时间:从程序运行开始到采样时
采样指标:alloc_space,alloc_objects,inuse_space,inuse_objects
计算方式:inuse = alloc - free
2.2.2 Goroutine-协程 & ThreadCreate-线程创建
Goroutine记录所有用户发起且在运行中的goroutine(即入口非runtime开头的) runtime.main的调用栈信息
ThreadCreate记录程序创建的所有系统线程的信息
Goroutine: Stop The World————>遍历allg切片————>输出创建g的堆栈————>Start The World
ThreadCreate:Stop The World————>遍历allm链表————>输出创建m的堆栈————>Start The World
2.3 性能调优实战-简介
介绍实际业务服务性能优化的案例
对逻辑相对复杂的程序如何进行性能调优
业务服务优化
基础库优化
Go语言优化
2.3.1 业务服务优化
基本概念
服务:能单独部署,承载一定功能的程序
依赖:service A的功能实现依赖Service B的响应结果,称为Service A依赖Service B
调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
基础库:公共的工具包、中间件
流程:
建立服务性能评估手段
分析性能数据,定位性能瓶颈
重点优化项改造
正确性是基础
响应数据diff
线上请求数据录制回放
新旧逻辑接口数据diff
优化效果验证
2.3.2 Go语言优化
编译器&运行时优化
优化内存分配策略
优化代码编译流程,生成更高效的程序
内部压测验证
推广业务服务落地验证
优点: 接入简单,只需要调整编译配置;通用性强。