高质量编程与性能调优实战 | 青训营笔记

59 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天。今天的课程主要关于Go语言的高质量编程和性能调优。

高质量编程

编码规范

推荐使用gofmtgoimports自动格式化代码。

注释的使用

  1. 注释应该解释代码作用。对外提供的代码应该描述它的功能和用途。同时也没有必要对显而易见的函数注释。
  2. 注释应该解释代码是如何做到。这指的是对代码中复杂的逻辑进行说明。
  3. 注释应该解释代码什么情况下会出错。也就是对方法中可能的异常进行说明。
  4. 公共符号始终要注释。但是不需要注释实现接口的方法。

命名规范

  • 简洁胜于冗长
  • 缩略词全大写,但是当位于开头且不导出时小写。
  • 变量被使用的越远,则应该携带越多的上下文信息。
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()

使用WrapWrap可以形成错误链。在fmt.Errorf中使用%w符号表示另一个错误。通过error.Is方法判断一个错误是不是特定的错误,这个方法会检查错误链中的所有错误。要获得特定种类的错误,使用error.As方法。

不建议在业务中使用panic

性能优化建议

首先使用BenchMark评估性能。对于基准测试,函数开头为Benchmark,传入参数*testing.B,测试指令为go test -bench=. -benchmem

对于Slice,尽可能预分配内存。make(slice)的函数包括长度和容量,容量应当预计好一定的大小。除此之外,在已有切片上创建新切片,不会创建新的底层数组,但可能导致原切片内存无法释放,需要综合考虑使用切片还是copy

Map同样可以预分配内存。

当需要频繁进行字符串拼接时,不应该只使用+。应该使用strings.Builderbytes.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循环上。因为它不实现功能,可以先注释掉。其他优化的方法基本类似。