这是我参与「第五届青训营 」伴学笔记创作活动的第3天
前言
本文主要讲解高质量编程的规范和性能调优分析工具pprof的使用
高质量编程
1.定义
编写的代码能够达到正确可靠、简洁清晰的目标。
- 完整性:各种边界条件是否考虑完善
- 稳定性:异常情况能进行处理
- 简单性:逻辑是否清晰
- 可读性:易懂易维护
- 生产力:团队统一标准,整体工作效率提升
不局限于go语言,每种语言开发都应遵循以上原则。
2.编码规范
主要从代码格式、注释、命名规范、控制流程、错误和异常处理这五个部分进行讲解。
代码格式:推荐使用gofmt自动格式化代码
注释:解释代码作用、做法(如何做的)、实现原因和出错情况(限制条件),其中公共符号始终要注释。
命名规范:
- 简洁大于冗余
- 缩略词全大写,但当位于变量开头且不需要导出时,采用小写
(
使用ServeHTTP而不是ServeHttp;使用XMLHTTPRequest而不是xmlHTTPRequest) - 变量名称需要携带更多上下文信息
- 函数不携带包名的上下文信息且尽量简短
- package只由小写字母组成,且不与标准库同名
控制流程:
- 避免嵌套,保持正常流程清晰
- 尽量保持正常代码路径为最小缩进Tab,方便检查
- 尽量简化复杂的条件语句和循环语句,避免故障出现
错误和异常处理:
- 简单错误:只出现一次的错误,且在其他地方不需要捕获的错误,优先使用errors.Now,若有格式化需求,则用fmt.Errorf
- 错误的Wrap和Unwrap:其实际上是提供了一个error嵌套另一个error的能力,从而生成一个error跟踪链
- 错误判定:判定错误是否为特定错误,采用
error.ls,该方法可判定错误脸上的所有错误是否含有特定的错误。
当在错误链上获取特定种类的错误时,采用error.As
- panic:比error更严重,在业务代码上不建议使用
- recover:只能在
defer的函数中使用;嵌套无法生效;只在当前goroutine生效;defer是一个栈,后进先出
3.性能测试建议
在满足正确可靠、简洁清晰等质量因素的条件下,进行综合评估,有时候时间效率和空间效率可能对立
Benchmark
性能表现需要实际数据衡量,采用Benchmark性能测试工具,下面是一个例子:
// from fib.go
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n - 1) + Fib(n - 2)
}
// from fib_test.go
func BenchmarkFib10(b *testing.B) {
// run the Fib funciton b.N times
for n := 0; n < b.N; n++ {
Fib(10
}
}
采用go test -bench=, -benchmen 进行测试,结果如下:
Slice预分配内存
- 尽量在
make()初始化时提供容量信息,执行时间大大减少
原因:切片本质是一个数组片段的描述,包括数字指针、片段长度和片段容量,其并不复制切片指向的元素,而是创建一个新切片来复用原来切片的底层数组
- 另一陷阱:大内存未释放
当原切片较大,代码在原切片基础上新建小切片,这导致原底层数组在内存中有引用进而得不到释放。
解决办法:可用copy替代re-slice
map预分配内存
和slice相似,make(map[int]int,size),加上size也会提高性能
由于不断向 map 中添加元素的操作会触发 map 的扩容,因此提前分配好空间可以减少内存拷贝和 Rehash 的消耗,并建议根据实际需求提前预估好需要的空间。
strings.Builder
字符串拼接的方法,比+更快,例子如下:
func StrBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
分析:由于字符串在 Go 语言中是不可变类型,占用内存大小是固定的。因此,使用+每次都会重新分配内存strings.Builder底层是 []byte 数组内存扩容策略,不需要每次拼接重新分配内存。
空结构体
节省内存,可作为各种场景的占位符来使用
func EmptyStructMap(n int) {
m := make(map[int]struct{})
for i := 0; i < n; i++ {
m[i] = struct{}{}
}
}
atomic包
由于锁的实现是通过操作系统来实现,属于系统调用。而atomic操作是通过硬件实现,效率比锁高。
常见操作:add、sub、read、wirte等
性能调优
保证正确性,定位主要瓶颈
1.原则
- 依靠数据而不是猜测
- 定位最大问题而不是小点
- 不要过早和过度优化
2.性能调优工具——pprof
pprof是用于可视化、分析性能和数据的工具
常见分析对象:cpu、heap、goroutine、mutex(锁)、block
3.性能调优方法
主要从下面三方面进行性能调优:
- 业务服务优化
- 基础库优化(例如,AB实验SDK的优化)
- Go语言优化(例如,编译器和运行时的优化)
业务服务优化
流程:
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈(例如,使用库不规范、高并发场景优化不足等)
- 重点优化项改造
- 效果验证
- 进一步优化,服务整体链路分析
小结
通过了解高质量编程,这让我更加重视代码命名、注释等的规范,并收获了一些代码逻辑技巧。此外,通过学习性能调优工具pprof,这让我们更加理解了代码的构造与运行,进而学习到了代码在编写后优化的方向。
参考
- 字节跳动青训营高质量编程与性能调优实战教程