高质量编程与性能调优 | 青训营笔记
这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记
01.高质量编程
1 编码规范
1.1 代码格式
使用gofmt。
Go语言的gofmt可以自动对代码进行格式修正,Goland集成开发环境自带这一功能,在保存代码的同时会自动对排版进行对齐。
1.2 注释
注意在一些必需的地方添加注释,方便别人阅读程序和错误排查
1.3 命名规范
变量、函数、类方法名的命名都需要遵守规则,让其简单易懂,无二义性。
1.4 控制流程
1.5 错误和异常处理
panic
- 不建议在业务中用panic
- error尽可能提供简明的上下文信息链,方便定位问题
- panic用于真正异常的情况
recover生效范围,在当前goroutine的被defer的函数中生效 (defer语句会在函数返回前调用;多个defer语句是后进先出的)
2 性能优化建议
2.1 Benchmark
基准性能测试工具Benchmark可以方便的对一些简单的场景进行性能测试,从而判断相应的优化能起多大的作用。
2.2 Slice
在已有切片的基础上再创建切片,不会创建新的底层数组,如果在大型切片的基础上多次切片,会导致原底层数组在内存中有引用,即使它已经不必存在主存中,也因为有引用而得不到释放。可以用copy方法替代切片,copy是深拷贝一个数组,不会和原数组产生引用关系。
2.3 Map
map预分配内存
- 不断向map中添加元素会触发map的扩容
- 提前分配好空间可以减少内存拷贝和rehash开销
- 建议根据实际需求提前预估好需要的空间
2.4 字符串处理
使用+拼接字符串性能最差,在性能优化场景建议使用strings.Builder,bytes.Buffer,其中
string.Builder更快。
bytes.Buffer转化为字符串时重新申请了一块空间。string.Builder直接将底层[]byte转换成了字符串类型返回。
使用Grow提前给定字符串大小
builder.Grow(n * len(str)
2.5 空结构体
使用空结构体节省内存。例如使用map时如果只需要键不需要值,可以用空结构体来对值赋值,这样编译后值不会占存储空间,因为空结构体为0字节。(即使是bool类型也会占用1字节,所以空结构体是可以节省内存的)
2.6 atomic包
在并发编程时可以考虑使用atomic包,atomic操作是通过硬件实现的,效率比锁高;而锁是通过操作系统来实现,属于系统调用。
小结:
- 避免常见的性能陷阱可以保证大部分程序的性能
- 普通应用代码,不要一味地追求程序的性能
- 越高级的性能优化手段越容易出现问题
- 在满足正确可靠、简洁清晰的质量要求的前提下提高程序性能
性能调优原则
- 要依靠数据不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
3 性能分析工具 ppof
使用ppof对一段程序的性能分析过程如下:
主要命令:top、list Eat、web
注意web命令需要安装Graphviz。
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
(pprof) top
Showing nodes accounting for 3.22s, 98.77% of 3.26s total
Dropped 29 nodes (cum <= 0.02s)
flat flat% sum% cum cum%
3.20s 98.16% 98.16% 3.22s 98.77% github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat
0.02s 0.61% 98.77% 0.02s 0.61% runtime.asyncPreempt
0 0% 98.77% 3.22s 98.77% github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Live
0 0% 98.77% 3.24s 99.39% main.main
0 0% 98.77% 3.24s 99.39% runtime.main
0 0% 98.77% 0.02s 0.61% runtime.systemstack
(pprof) list Eat
Total: 3.26s
ROUTINE ======================== github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat in C:\Users\administration\go\pkg\mod\github.com\wolfogre\go-pprof-practice@v0.0.0-20190402114113-8ce266a210ee\animal\felidae\tiger\tiger.go
3.20s 3.22s (flat, cum) 98.77% of Total
. . 19:}
. . 20:
. . 21:func (t *Tiger) Eat() {
. . 22: log.Println(t.Name(), "eat")
. . 23: loop := 10000000000
3.20s 3.22s 24: for i := 0; i < loop; i++ {
. . 25: // do nothing
. . 26: }
. . 27:}
. . 28:
. . 29:func (t *Tiger) Drink() {
flat是函数自身的消耗;cum是本身和调用函数的消耗。 如果flat==cum,说明函数中没有调用其他函数;flat==0,函数中只有其他函数的调用.
3.1 pprof-排查实战
- alloc_objects: 程序累计申请的对象数
- alloc_space: 程序累计申请的内存大小
- inuse_objects: 程序当前持有的对象数
- inuse_space: 程序当前占用的内存大小
goroutine-协程
http://localhost:6060/debug/pprof/goroutine
火焰图从上到下表示调用顺序,每一个块表示一个函数,越长代表占用cpu时间越长。火焰图是动态的,支持点击块进行分析。
mutex-锁
命令: go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
block-阻塞
命令: go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
3.2 性能分析工具pprof-采样过程和原理
1.CPU
2.Heap-堆内存
性能调优案例
业务服务优化
基础库优化
AB实验SDK的优化
Go语言优化
总结
性能调优原则:要依靠数据不是猜测。 擅用性能分析工具pprof。