这是我参与「第五届青训营 」笔记创作活动的第4天
Day4:Go高质量编程与性能调优
高质量编程
——编写的代码能够达到正确可靠、简洁清晰的目标
比如是否考虑边界、异常处理、易读易维护等
原则:简单性、可读性、生产力
-
代码格式:gofmt自动格式化,goimports工具(还可以自己更新依赖包)
-
注释:公共符号需要注释,要保证注释提供代码未表达出的上下文信息。注释要解释代码实现的过程和原因(补充一些上下文),解释代码出错的情况。
-
命名
- 变量:简洁胜于冗长;缩略词全大写,位于变量开头且不需要导出,用全小写(ServerHTTP, xmlServer);距离被使用的地方越远,需要的上下文越多。
- 函数:不携带包的上下文,因为他们经常成对出现。尽量短。当函数返回值类型与包名一致时,可以考虑在函数名中加入类型信息(如CutString等)
- package:只由小写字母构成,无大写字母和下划线。简短。不要与便准库同名。不使用常见变量名,尽量使用单数而非复数,谨慎使用缩写。
-
控制流程:线性原理,处理逻辑尽量走直线。避免嵌套,优先处理错误和特殊情况,尽早返回来减少运行次数。
-
错误和异常处理:简单错误用errors.New(),有格式化要求就用fmt.Errorf(错误链用%w)。与错误链路相关的可以有errors.is和errors.as的使用。不建议使用Panic,因为它会让程序直接崩溃,通常只用在启动阶段使用(init和main函数)
recover只能在被defer的函数中使用
...
defer func(){
if e := recover(); e != nil {
//恢复失败的逻辑
err = fmt.Errorf("gitfs panic: %v\n%s", e, debug.Stack())//打印recover信息和调用栈
}
}()
...
defer语句的特性是后进先出,以下面的例子解释,输出结果为31
func main(){
if true {
defer fmt.Printf("1")
}else {
defer fmt.Printf("2")
}
defer fmt.Printf("3")
}
性能优化建议
工具
Go提供了支持基准性能测试的benchmark工具
go test -bench=. -benchmem
Slice预分配内存——提供cap信息
data := make([]int, 0, size)
优于
data := make([]int, 0)
copy替代reslice
copy(dst slice, sou slice)
通过大切片创造小切片时,使用copy会性能更优
map预分配内存
data := make(map[int]int, size)
优于
data := make(map[int]int)
如果我们可以提前预估的话,提供好size会极大的提高运行效率,因为扩容时map会有内存拷贝和rehash,这都是极大的消耗。
字符串拼接
METHOD 1:
s := ""
for {
s += [string]
}
return s
METHOD 2:
var builder strings.Builder
for {
builder.WriteString([string])
}
return builder.String()
METHOD 3:
var buf := new(bytes.Buffer)
for {
buf.WriteString([string])
}
return buf.String()
从测试结果来看,后两种方法明显优于前两者
为什么?字符串是不可变类型通过+=操作会不断地分配新的空间(恰好放下新的字符串),所以这是极其低效的,而后两种会更好,而strings.Builder更好的原因在于,它直接把 []byte转换成字符串类型,没有新分配,但是bytes.buffer不一样,它会重新申请一块空间放最终生成的字符串。
能不能更优?可以使用builder.Grow([size]) or buf.Grow([size])来预分配内存
空结构体不占据任何内存
m := make(map[int]struct{}) —— 键值不占据任何空间,仅为占位符
优于
m := make(map[int]bool) —— 键值还有一个字节
可以用这个特性实现Set类型,因为只需要用到key,不需要用value
atomic包
atomic是通过硬件实现的,效率比通过操作系统实现的锁效率高
[有待后续学习]
性能调优工具
原则
- 依靠数据
- 定位最大瓶颈而非细枝末节
- 不要过早优化和过度优化
pprof工具
安装配置参考:www.manongjc.com/detail/54-t…
排查CPU
运行命令
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"//运行工具
top//展开详情
list ...//根据指定的正则表达式查找代码行
web//调用关系可视化
一些参数
如果flat == cun 函数中没有调用其它函数
如果flat == 0 函数中有调用其它函数
排查内存
命令
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
一些数据
排查协程
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
排查mutex / block
命令——修改相应的后缀即可
性能调优案例
——通常适用于逻辑相对复杂、需求量相对大的情况
基本概念
服务:能够单独部署,承载一定功能的程序
依赖:A的功能实现依赖于B的响应结果
调用链路:支持一个接口请求的相关请求服务集合及依赖关系
基础库:公共的包等
评估流程
建立评估手段——分析数据,定位瓶颈——重点项改造——优化结果验证
基础库优化 & Go语言优化(自由度高)