这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天。今天的课程主要关于Go语言的高质量编程和性能调优。
高质量编程
编码规范
推荐使用gofmt和goimports自动格式化代码。
注释的使用
- 注释应该解释代码作用。对外提供的代码应该描述它的功能和用途。同时也没有必要对显而易见的函数注释。
- 注释应该解释代码是如何做到。这指的是对代码中复杂的逻辑进行说明。
- 注释应该解释代码什么情况下会出错。也就是对方法中可能的异常进行说明。
- 公共符号始终要注释。但是不需要注释实现接口的方法。
命名规范
- 简洁胜于冗长
- 缩略词全大写,但是当位于开头且不导出时小写。
- 变量被使用的越远,则应该携带越多的上下文信息。
for i := 0; i < n; i++ {
}
// 而不是
for index := 0; index < n; index++ {
}
变量名需要保留足够的信息量
func (c *Client) send(req *Request, deadline time.Time)
// 而不是
func (c *Client) send(req *Request, t time.Time)
函数名不携带包名的上下文信息,尽量简短。
package只由小写字母组成,不包含大写字母和下划线,简短并有一定上下文信息,不要与标准库同名。
控制流程
避免嵌套,保持流程清晰。
if foo {
return x
}
return nil
// 而不是
if foo {
return x
} else {
return nil
}
错误情况优先处理,尽量保证正常代码路径为最小路径。
异常处理
简单,只出现一次的错误直接使用errors.New(),如果需要格式化,使用fmt.Errorf()。
使用Wrap和Wrap可以形成错误链。在fmt.Errorf中使用%w符号表示另一个错误。通过error.Is方法判断一个错误是不是特定的错误,这个方法会检查错误链中的所有错误。要获得特定种类的错误,使用error.As方法。
不建议在业务中使用panic。
性能优化建议
首先使用BenchMark评估性能。对于基准测试,函数开头为Benchmark,传入参数*testing.B,测试指令为go test -bench=. -benchmem。
对于Slice,尽可能预分配内存。make(slice)的函数包括长度和容量,容量应当预计好一定的大小。除此之外,在已有切片上创建新切片,不会创建新的底层数组,但可能导致原切片内存无法释放,需要综合考虑使用切片还是copy。
Map同样可以预分配内存。
当需要频繁进行字符串拼接时,不应该只使用+。应该使用strings.Builder或bytes.Buffer,会对性能有很大的影响。这是由于Go字符串不可变,每次+都会分配内存,而Builder或Buffer有自己的扩容策略。
当需要进行共享内存的多线程编程时,应当使用atomic包。因为操作系统硬件层面支持原子操作,而Mutex是软件层面的,会有性能差距。
性能调优
使用pprof监控程序性能。引入库后,在main中使用如下代码引入pprof,并在web端展示监控内容。
go func() {
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
这里设置了在6060端口上监控,运行程序后浏览器访问localhost:6060/debug/pprof可以看到性能情况。但是这些性能指标是难以阅读的,需要借助其他工具进行阅读。案例主要针对CPU、堆内存、goroutine,锁竞争和阻塞操作。
首先是CPU,使用go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10指令采样十秒之后获取汇总文件。之后进入交互界面。输入top查看CPU占用最多的函数。
(pprof) top
Showing nodes accounting for 4.88s, 99.80% of 4.89s total
Dropped 1 node (cum <= 0.02s)
flat flat% sum% cum cum%
4.88s 99.80% 99.80% 4.89s 100% github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat
0 0% 99.80% 4.89s 100% github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Live
0 0% 99.80% 4.89s 100% main.main
0 0% 99.80% 4.89s 100% runtime.main
通过这里可以看到tiger.Eat占用了几乎所有的CPU资源。
输入list Eat可以查找Eat函数以及每行的占用情况。
Total: 4.89s
ROUTINE ======================== github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat in /home/adamska/projects/go-pprof-practice/animal/felidae/tiger/tiger.go
4.88s 4.89s (flat, cum) 100% of Total
. . 19:}
. . 20:
. . 21:func (t *Tiger) Eat() {
. . 22: log.Println(t.Name(), "eat")
. . 23: loop := 10000000000
4.88s 4.89s 24: for i := 0; i < loop; i++ {
. . 25: // do nothing
. . 26: }
. . 27:}
. . 28:
. . 29:func (t *Tiger) Drink() {
可以看到占用基本在这个for循环上。因为它不实现功能,可以先注释掉。其他优化的方法基本类似。