这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
今天课程主要内容是高质量编程及性能优化:
1. 高质量编程简介
2. 编码规范
3. 性能优化建议
4. 性能分析工具pprof实战
5. 性能调优案例
注:笔记图片来自课程截图,如有侵权,请联系删除
1.高质量编程简介
编写的代码能够达到正确可靠、简洁清晰的目标就是高质量代码。 包含考虑各种边界条件是否完备;异常情况处理,稳定性保证;易读易维护。 编程原则包括简单性、可读性、以及保证生产效率。
2.编码规范
代码格式
推荐使用gofmt自动格式化代码。gofmt:Go语言官方提供的工具,能自动格式化语言代码为官方统一风格常见IDE都支持方便的配置。goimports:是Go语言官方提供的工具,实际等于Gofmt机上依赖包管理,自动增删依赖的包引用、将依赖包按字母排序分类。
注释
注释应该:
- 解释代码作用:适当注释公共符号(全局)
- 解释代码如何做的:适当注释实现过程
- 解释代码实现原因:解释代码外部因素,提供额外上下文
- 解释代码什么时候会出错:解释代码限制条件
公共符号始终要注释,即包中声明的每个公共符号:变量、常量、函数以及结构都需要注释。任何不明显也不简短的公共功能也必须予以注释。无论长度或者复杂程度如何,对库中的任何函数都必须进行注释。但有一个例外,不需要注释实现接口的方法,如下:
还有就是结构有声明,紧跟着的结构方法可以不用注释,结构体已经明确了整个结构体的作用。
命名规范
变量:简介胜于冗长,缩略词全大写,但在变量开头且不是公共时,使用全小写例如ServerHTTP、xmlHTTPRequest。变量距离被使用的地方越远,需要携带越多的上下文信息,全局变量在名字中需要更多上下文信息方便识别。for以及if里,变量作用域小,因此越简洁越好。
函数:函数名不携带包名的上下文,因为包名和函数名总是成对出现;函数名应该尽量简单短;当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义;当名为foo的包某个韩式返回类型T,可以在函数名中加入类型信息。
包:只有小写字母组成,不包含大写字母和下划线等字符;简短并包含一定上下文信息;不要与标准库同名。尽量满足以下要求:
控制流程
应该避免嵌套,保持正常流程清晰,例如两个分支都包含return语句,可以去除冗余else;尽量保持正常代码路径为最小缩进。处理逻辑尽量走直线,避免复杂嵌套。
错误和异常处理
简单错误:仅出现一次的错误,在其他地方不需要捕获该错误,有限使用errors.NEW来创建匿名变量来直接表示简单错误,有格式化需求,使用fmt.Errorf。
错误的Wrap和Unwrap:错误的Wrap实际上提供了一个error嵌套另一个error的能力,从而生成一个error跟踪连,在fmt.Errorf中使用%w关键字来将一个错误关联到错误链中。
错误判定:判定一个错误是否是特定错误,使用error.Is;不同于使用==,使用该方法可以判定错误链上的所有错误是否含有特定错误。
在错误链上获取特定种类错误,用
errors.As:
panic:不建议在业务代码使用,调用函数不包含reover会造成程序崩溃,问题可以被屏蔽或解决建议用error代替panic。但是当程序启动阶段发生不可逆转错误,可以在init或main函数中使用。
recover:只能在被defer的函数中使用,嵌套无法生效,只在当前goroutine生效,defer后进先出。
如果需要更多上下文信息,可以recover后在log中记录当前的调用栈。
3. 性能优化建议
性能优化是综合评估,有时时间和空间相互对立
benchmark
基准性能测试工具,使用go test -bench命令测试基准
slice
尽可能在使用make()初始化切片时提供容量信息。切片包括数组指针,片段长度以及片段容量,切片复用底层数组,因此建议使用copy。
map
make时提供容量信息,不断增加会触发map扩容,提前分配空间可以减少内存拷贝和rehash消耗。
字符串处理
使用+性能差,string.Builder和bytes.Buffer相近,string.Builder更快,Go中语言字符串是不可变,内存大小固定。类型两者底层都是byte数组,内存扩容策略,不需要每次拼接重新分配内存。string.Builder更快,因为实现是直接byte数组转换成字符串类型返回,但是bytes.Buffer转化成字符串重新申请一块空间。
空结构体
空结构体struct{}实例不占据任何的内存空间,可以用来实现set。
atomic包
锁的实现是通过操作系统,属于系统调用。而atomic操作时通过硬件实现,效率更高,sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量,对于非数值操作,可以用atomic.Value,能承载一个interface{}。
4. 性能分析工具pprof实战
pprof是用于可视化和分析性能、数据的工具。能够通过浏览器查看指标。
CPU
终端输入go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"profile表示采样对象为CPU使用。seconds=10表示采样10秒,默认是60秒。
终端输入top可以查看CPU占用最高函数。显示结果flat表示当前函数本身的执行耗时;flat%表示flat占CPU 总时间的比例;sum%表示上面每一行的flat%总和;cum指当前函数本身加上其调用函数的总耗时;cum%表示cum占CPU总时间的比例。Flat==Cum,函数中没有调用其他函数。Flat==0,函数中只有其他函数的调用。
终端输入list后接正则表达式可以根据指定正则表达式查找代码行。web命令调用关系可视化。
Heap-堆内存
go tool pprof -http=8080"http://localhost:6060/debug/pprof/heap"另一种展示方式,会开启Web UI,性能指标会以网页形式呈现。可以菜单切换到Top视图同CPU,还有Source视图能显示代码。sample菜单,堆内存提供了四种指标:alloc_objects:程序累计申请的对象数;inuse_objects :程序当前持有的对象数;alloc_space:程序累计申请的内存大小;inuse_space:程序当前占用的内存大小
goroutine-协程
goroutine很容易泄露,会导致内存泄漏。go tool pprof -http=8080"http://localhost:6060/debug/pprof/goroutine"view菜单有个Flame Graph,从上到下表示调用,顺序每一块代表一个函数,越长代表占用CPU的时间更长火焰图是动态的,支持点击块进行分析。可以在Source视图搜索代码。
mutex-锁
go tool pprof -http=8080"http://localhost:6060/debug/pprof/mutex"
block-阻塞
go tool pprof -http=8080"http://localhost:6060/debug/pprof/block"
会显示阻塞数目,但是http中阻塞操作的测试会忽略。
最后又分别讲了上述性能的采样过程与原理。
5. 性能调优案例
介绍实际业务服务性能优化的案例,对逻辑相对复杂的程序如何进行性能调优。
业务服务优化
流程:建立服务性能评估手段;分析性能数据,定位性能瓶颈;重点优化项改造;优化效果验证。
基础库优化
分析基础库核心逻辑和性能瓶颈(设计完善改造方案、数据按需获取、数据序列化协议优化),内部压测验证,推广业务服务落地验证。
Go语言优化
编译器以及运行时的优化:优化内存分配策略;优化代码编译流程,生成更高效的程序;内部压测验证;推广业务服务落地验证。
优点:接入简单,只需要调整编译配置;通用性强。