这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
课程导学链接
【Go 语言原理与实践学习资料(上)】第三届字节跳动青训营-后端专场 - 掘金
课程项目地址
wolfogre/go-pprof-practice: go pprof practice. (github.com)
课程PPT
高质量编程与性能调优实战.pptx - 飞书云文档 (feishu.cn)
课程概要
-
介绍编码规范,帮助大家写出高质量程序
-
介绍 Go 语言的性能优化建议,分析对比不同方式对性能的影响和背后的原理
-
讲解常用性能分析工具 pprof 的使用和工作原理,熟悉排查程序性能问题的基本流程
-
分析性能调优实际案例,介绍实际性能调优时的工作内容
高质量编程
简介
编写的代码达到正确可靠、简洁清晰的目标,包括各种边界条件是否考虑完备、异常情况处理、稳定性、保证易读易维护
编程原则
编码规范
-
代码格式:gofmt和goimports使用
-
注释:应让代码成为最好的注释,注释应该提供代码未表达出的上下文信息
- 解释代码作用:公共符号的注释
- 解释代码实现过程
- 解释代码实现的原因:解释代码的外部因素并提供额外上下文
- 解释代码什么情况会出错:性能隐患、输入限制条件,让使用者无需了解实现细节
-
命名规范:降低代码阅读的成本,结合上下文信息设计简洁清晰的名称
- variable:简洁;缩略词全大写,但当其位于变量开头且不需导出时使用全小写;距离使用的位置越远,越需携带相关信息
- function:尽量简短;不携带包名信息;当名为foo 的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义;当名为foo的包某个函数返回类型T时(T并不是Foo),可以在函数名中加入类型信息
- package:包名应全部为小写;如果含义明显,名称应缩写(如fmt);尽量使用一个词,如果需要两个词,不应该用下划线分隔,第二个词也不应该大写(strconv包就是一个例子);不要使用包用户可能也想使用的名称
-
控制流程
- 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
- 尽量保证正常代码缩进为最小缩进
-
错误和异常处理(error、panic和recover的使用)
-
简单错误:仅出现一次的错误,且在其他地方不需要捕获该错误
优先使用errors.New来创建匿名变量来直接表示简单错
如果有格式化的需求,使用fmt.Errorf
-
错误的Warp和Unwarp(go1.13后):
错误的Wrap提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
可以在 fmt.Errorf中使用 %w 关键字来将一个错误关联至错误链中
-
错误判定:
判定一个错误是否为特定错误,使用errors.Is,不同于使用==,该方法可以判定错误链上的所有错误是否含有特定的错误
在错误链上获取特定种类的错误,使用errors.As
-
panic:
不建议在业务代码中使用panic
调用函数不包含recover会造成程序崩溃
若问题可以被屏蔽或解决,建议使用error代替panic
当程序启动阶段发生不可逆转的错误时,可以在 init或main函数中使用panic
-
recover:
recover 只能在当前goroutine的被defer 的函数中生效,嵌套无法生效
defer的语句是后进先出
如果需要更多的上下文信息,可以recover后在 log 中记录当前的调用栈
-
性能优化建议
-
简介
- 性能优化的前提是满足正确可靠、简洁清晰等质量因素
- 性能优化是综合评估,有时候时间效率和空间效率可能对立
- 针对GO语言特性,介绍Go相关的性能优化建议
-
Benchmark:利用基准性能测试工具的测量数据衡量性能
-
Slice:
-
创建slice时根据预计容量指定cap容量以减少自动扩容次数
-
大内存未释放陷阱:在已有切片基础上创建切片,不会创建新的底层数组场景
原切片较大,代码在原切片基础上新建小切片
原底层数组在内存中有引用,得不到释放
可使用copy替代 re-slice
-
-
Map:同Slice应根据1预计容量来预分配内存
-
string处理:使用+拼接性能最差,strings.Builder与bytes.Buffer 相近,strings.Builder更快
- 字符串在Go语言中是不可变类型,占用内存大小是固定的;使用+每次都会重新分配内存
- strings.Builder,bytes.Buffer底层都是[]byte数组,内存扩容策略,不需要每次拼接重新分配内存
- bytes.Buffer转化为字符串时重新申请了一块空间,strings.Builder 直接将底层的[]byte 转换成了字符串类型返回
- 能预估最终长度时,能进一步提升效率
-
空结构体
- 空结构体struct的实例不占据任何的内存空间
- 可作为各种场景下的占位符使用,如实现set、channel接收发送数据
-
atomic包
- 锁的实现是通过操作系统来实现,属于系统调用;atomic操作是通过硬件实现,效率比锁高
- sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
- 对于非数值操作,可以使用atomic.Value,能承载一个interface{}
-
总结
- 避免常见的性能陷阱可以保证大部分程序的性能
- 普通应用代码,不要一味地追求程序的性能
- 越高级的性能优化手段越容易出现问题
- 在满足正确可靠、简洁清晰的质量要求的前提下提高程序性能
性能调优实战
性能调优原则
- 要依靠数据不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早和过度优化
pprof
-
功能介绍
-
排查实战:结合导学链接+PPT进行操作即可;可以阅读下面的博客获得更详细的过程
golang pprof 实战 | Wolfogre's Blog
注意从GitHub中clone下来的项目中没有go.mod文件的,而go1.18强制使用module来管理项目,需要我们自己生成go.mod文件
$ go mod init github.com/wolfogre/go-pprof-practice $ go mod tidy