高质量编程简介以及性能调优 | 青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
内容目录
- 高质量编程
- 性能调优
高质量
编写的代码能够达到正确可靠、简洁清晰的目标可以称之为高质量代码。
什么是高质量?
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
Go语言编程原则
- 简单性:消除“多余的复杂性”,以简单清晰的逻辑编写代码
- 可读性:代码是写给人看的,而不是机器;编写可维护代码的第一步是确保代码可读
- 生产力:团队整体工作效率非常重要
代码规范
- 代码格式:
- gofmt,go语言官方提供的工具,能够自动格式化Go语言的代码为官方的统一风格
- goimports:也是go语言官方提供的工具,实际等于gofmt加上依赖包管理
- 注释:
- 公共符号(函数,结构体等)始终需要注释
- 注释应该解释代码的作用
- 注释应该解释代码是如何做的:解释简单的逻辑,在函数体当中,需要解释的地方。
- 注释应该解释代码实现的原因
- 注释应该解释代码什么情况会出错:比如对变量传入的一些限制等
- 命名规范:
- 变量名称
- 简洁胜于冗长
- 缩略词全部大写,当其期位于变量开头且不需要导出的时候,使用全小写:go语言当中导出属性需要是首字母大写的,否则外部无法访问,同时结构体如果需要被json或者bson识别,也需要将其中的变量名首字母大写,否则是无法进行填入的,因为这些变量都不是公有的
- 例如使用ServeHTTP 而不是 ServeHttp
- 函数名
- 变量距离其被使用的地方越远,则需要携带越多的上下文信息
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现。
- 函数名尽量简短。
- 包名
- 不使用常用变量名作为包名,例如使用bufio而不是buf
- 使用单数而不是复数
- 谨慎的使用缩写
- 小结:
- 核心目标是降低阅读理解代码的成本
- 重点考虑上下文信息,设计简洁清晰的名称
- 变量名称
- 控制流程
- 尽量保持正常代码路径为最小缩进
- 减少嵌套分支。
- 错误和异常处理
- 简单错误
- 简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
- 优先使用errors.New 来创建匿名变量来直接表示简单错误。
- 如果格式化的需求,使用fmt.Errorf
- 错误的Wrap和Unwrap
- 错误判定
- recover
- panic
- 简单错误
练习
看下面一段代码
package main
import "fmt"
func main() {
if true {
defer fmt.Println("1")
} else {
defer fmt.Println("2")
}
defer fmt.Println("3")
}
运行结果是什么
这里可以发现其实defer类似于栈的结构,先defer的会后执行,后defer的先执行
性能优化
- 性能优化的前提是满足正确可靠、简洁清晰等质量因素。
- 性能优化是综合评估,有时候时间效率和空间效率可能对立。
- 针对Go语言特性,介绍Go相关性能的优化建议
Benchmark 基准性能测试工具
slice优化
slice的底层数据结构
type slice struct{
array unsafe.Pointer
len int
cap int
}
在扩容的时候会增加时间消耗,所以我们在使用slice的时候可以采用预分配内存的机制,然后得到结果。
还有一个问题就是大内存未释放,在已有的切片基础上创建切片,不会创建新的底层数组,这里其实可以认为,我们本质上不过是调整了一个指针位置指向我们想要的部分,如果我们进行操作,还是在原来的切片上进行的操作。
- 原切片比较大,代码在原切片的基础上创建小切片
- 原底层数组在内存中有引用,得不到释放(不可被垃圾回收机制识别)
就像上面这样的逻辑,由于origin[len(origin)-2 : ],不会开辟新的空间,不过是将指针调整到了origin[len(origin)-2]的位置,所以利用下面这种copy可以很好的解决问题。
map优化
map的底层数据结构 也需要预分配内存
字符串处理
- 采用左边的方式的时候会开辟一个新内存空间
- 采用右侧的方式维护了一个扩容的策略,底层都是[]byte数组。
空结构体
用于实现一个set,主要是进行查重,利用map的键值是不能重复的这个特性。
atomic包
pprof工具进行性能调优
参考:golang使用系列---- net/http/pprof (kingjcy.github.io)
这里我们利用样例程序进行测试
优化CPU占用
运行样例程序,执行指令运行pprof命令行窗口
然后利用top指令进行查看
看到了上面的效果,得到的列表参数含义如下:
观察上面的列表: Flat == Cum 函数中没有调用其他函数 Flat == 0 函数中只有其他函数的作用
然后我们通过上面的情况可以知道,第一个tiger.(*Tiger).Eat函数调用占用了最多的资源,我们通过list Eat来观察,结果如下:
我们可以看到,这里的问题在于,我们写了一个没有进行任何操作的循环,所以我们将这个部分注释掉,再重新运行。
也可以通过web命令可以查看调用关系图
此时注释掉之后运行,对cpu的优化很好,但是占用内存仍然很多,所以下面来优化内存的问题。
优化内存占用
这里使用图形化工具
source视图
我们在这里发现 mouse.go 当中的函数出现了问题,我们将那一句注释掉,就可以解决一部分的问题
当然内存优化还没有结束,我们还可以优化内存的其他方面
然后上面的问题就是因为申请空间之后并不使用,所以我们可以注释掉这一句,解决这里的问题。
下面是利用命令行来进行分析
goroutine 协程优化
火焰图
mutex-锁 优化
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
block-阻塞 优化
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
总结
关于一些问题
在利用图形化工具的时候出现了以下的问题:
下面的链接有解决方式: Could not execute dot; may need to install graphviz. – SRE笔记
关于代码优化过程中自动过滤的问题:
程序正常运行,占比较小的函数会被过滤掉:
隐藏可能被过滤掉的元素可以从图形化工具当中查看。