优化前提
基础功能
架构设计
硬件资源
基本思路
思想同c/c++系统优化基本相同
CPU密集型/IO密集型
先分析程序是属于CPU密集型还是IO密集,如果是IO密集型,数据读写是否可使用内存盘、固态盘? 如果是CPU密集型,单线程程序是否可以改造成多线程? 多线程是否存在抢锁,或者同步? 是否可以减小锁粒度设置实现无锁设计
函数高频调用
高频调用的函数性能能有所提升,基本上可直接提升程序的整体性能
锁粒度
对于高性能计算程序,最好不要设计多线程抢锁的程序架构,如果锁设计不可避免那是否可以优化到减小锁粒度?
线程池/协程池
如果程序中存在大量创建线程的情况,可加一个线程池,减小创建线程的消耗在一定程序上可提高系统吞吐量。在Go中虽然创建协程开销很小,但是系统创建大量程序可能奔溃
对象池
如果程序中不可避免的创建大量小对象,可用对象池来减小GC压力,类似内存池
Profiling
Golang 提供的两个官方包 runtime/pprof,net/http/pprof 能方便的采集程序运行的堆栈、goroutine、内存分配和占用、io 等信息的 .prof
文件,可以使用 go tool pprof 分析 .prof
文件。两个包的作用是一样的,只是使用方式的差异。对于线上服务通常是使用net/http/pprof 通过暴露外部接口在必要时通过访问接口是profile; runtime/pprof可以在go test压测的时候用。
通过profile可以查看cpu/heap/gorotinue/stack/threadcreate/block/mutex的信息
pprof示例:
- runtime pprof
通常用于一次性运行的profile分析
1 | import "runtime/pprof" |
- go test bench
go bench压测可生成cpu/mem的profile文件,压测时的profile分析
1 | go test -bench . -benchmem -cpuprofile prof.cpu -memprofile prof.mem |
- http pprof
通常用于后台服务的profile分析
1 | package main |
pprof 实现
pprof不会实时采集程序数据,而是在pprof端口有访问的时候才会开始采集,所以在程序中开启pprof并不会实时采集导致程序性能下降。
- http路由
1 | // net/http/pprof.go |
- 各种profile对应的采集方法
1 |
|
Profile 概览
通过访问debug/pprof/heap可以大体上掌握程序运行时状态,web访问示例: http://ip:19194/debug/pprof
1 | /debug/pprof/ |
pprof 详情
使用函数调用图/矢量图/动态top等手段来查看程序当前运行情况
google pprof相比go tool中字段的pprof工具会更好用,相比go tool pprof工具,该工具集成了更好的web功能,方便生成矢量图以及火焰图,如果用go tool pprof工具在生成火焰图时需要手动安装go torch组件
cpu(CPU Profiling):
$HOST/debug/pprof/profile
,默认进行 30s 的 CPU Profiling,得到一个分析用的 profile 文件。报告程序的 CPU 使用情况,按照一定频率去采集应用程序在 CPU 和寄存器上面的数据block(Block Profiling):
$HOST/debug/pprof/block
,查看导致阻塞同步的堆栈跟踪。报告 goroutines 不在运行状态的情况,可以用来分析和查找死锁等性能瓶颈goroutine:
$HOST/debug/pprof/goroutine
,查看当前所有运行的 goroutines 堆栈跟踪heap(Memory Profiling):
$HOST/debug/pprof/heap
,查看活动对象的内存分配情况mutex(Mutex Profiling):
$HOST/debug/pprof/mutex
,查看导致互斥锁的竞争持有者的堆栈跟踪threadcreate:
$HOST/debug/pprof/threadcreate
,查看创建新OS线程的堆栈跟踪
CPU
CPU: pprof -seconds=60 -http=”:8081” ./logbeat http://ip:19194/debug/pprof/profile
矢量图graph:
![image-20190505234653119](/Users/knife/Library/Application Support/typora-user-images/image-20190505234653119.png)
flat:给定函数上运行耗时
flat%:同上的 CPU 运行耗时总比例
sum%:给定函数累积使用 CPU 总比例
cum:当前函数加上它之上的调用运行总耗时
cum%:同上的 CPU 运行耗时总比例
![image-20190506112408391](/Users/knife/Library/Application Support/typora-user-images/image-20190506112408391.png)
CPU火焰图:
![image-20190506112138466](/Users/knife/Library/Application Support/typora-user-images/image-20190506112138466.png)
Heap
![image-20190517145840811](/Users/knife/Library/Application Support/typora-user-images/image-20190517145840811.png)
1 | // runtime.MemProfile() |
Goroutine
1 | pprof -seconds=60 -http=":8082" ./logbeat http://ip:19194/debug/pprof/goroutine |
![image-20190520175255103](/Users/knife/Library/Application Support/typora-user-images/image-20190520175255103.png)
优化点
- 字符串拼接效率低,采用buffer.WriteString
- time.LoadLocation非常耗时,会打开时区文件进行解析
- go map/slice 最好可预先分配大小
- 尽量采用[]byte替代string,减少[]byte和string之间想换转换
- goroutinue池代替大量创建goroutinue
- 多用channel实现同步,尽量不用互斥锁