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

114 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天


高质量编程与性能调优

高质量编程

一个优秀的程序员需要具备良好的编程素养。学习完本课后我总结了以下几点编程规范,这也是我们小组开发时要遵守的基本编程规则。

代码规范

  • 格式
    • 推荐使用 GoLand 开发,代码保存时自动格式化
    • 其他 IDE 使用 gofmt+goimports 对代码格式化
  • 注释
    • 包中声明的每个公共符号(变量、常量、结构体、函数)必须注释
    • 对于实现接口的方法不需要注释,但其接口方法的定义部分必须注释
    • 对于方法的注释,应诠释代码作用、代码参数意义、代码返回错误的场景
  • 命名规范
    • 使用驼峰命名法,缩略词全大写(ServerHTTP),但当其不需要导出时全小写(xmlHTTPRequest)
    • 不允许使用拼音与意义不明的单一字符
  • 控制流程
    • 多使用,合理使用设计模式
    • 参数检查与功能实现需分离,如使用
      if param == nil {
          return nil  
      }
      doSome(param)
      
      而非
      if param != nil {
          doSome(param)
      }
      
    • 在 if-else 逻辑中,应该先判断简单、代码量少的操作逻辑,将复杂的,大段的逻辑留在分支的最后
      if !enable {
          doEnable()
      } else {
          loadConfiguration()
          doSetup()
          doBroadcast()
          // ......
      }
      
  • 错误与异常处理
    • 简单异常直接使用 errors.Newfmt.Errorf 处理即可
    • 对于存在异常上下文的嵌套错误,必须使用 fmt.Errorf,用 %w 占位符将错误加入错误链中
    • 异常分类:
      • error 表示上下文异常,便于处理预料到的错误逻辑
      • panic 代表程序产生了影响运行的真正错误
      • recover 仅defer中有效,可重新获取对程序的控制

单元测试

  • 所有关键函数必须增加单元测试,测试覆盖率必须在 50% +,接口覆盖率必须达到 80% +
  • 测试分支需要满足相互独立,全面覆盖

性能优化

性能优化建议

  • slice
    • 预分配内存 - #make 初始化切片时提供容量信息,减少内存拷贝
    • 在已有切片上创建切片,原切片底层数组仍被引用,资源未被释放 -> copy 代替 re-slice
  • map
    • 预分配内存 - #make 初始化map时提供容量信息,减少内存拷贝和 rehash 消耗
  • string
    • 拼接字符串使用 strings.Builder、bytes.Buffer
    • 使用 builder#Grow 预分配大小进一步提升效率
  • struct{} 空结构体
    • 空结构体实例不占据任何内存,可作为任何场景下的占位符使用 -> set 实现,对于 map 结构只需要 key,不关心 value
  • 原子操作
    • atomic 硬件实现,效率更高,推荐用于保护变量
    • sync.Mutex 通过操作系统实现,属于系统调用,效率更低,语义上来说更应该保护逻辑而非变量

Benchmark 相关参数解读

BenchmarkFlib-16      5800983          206.1 ns/op
-16 为 GOMAXPROCS,1.5 后默认为 cpu 核数
5800983 实际执行次数
206.1 ns/op 每次执行的时间
0 B/op 每次申请多大的内存
0 allocs/op 每次执行申请几次内存

调优工具 pprof

在本节课中,使用到里 github 上的测试项目实地的学习 pprof 的调优过程。

cpu问题排查过程

  1. cpu 占用检查:go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
  2. 根据指定的正则表达式查找代码行
    (pprof) list Eat
    
  3. 调用关系可视化
    (pprof) web
    
  4. 排查掉 trigger.go

pprof 命令行介绍

(pprof) top
flat  flat%   sum%        cum   cum%
4.52s   100%   100%      4.52s   100%  github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat
  • flat - 函数本身耗时
  • flat% - flat 占 cpu 总时间的比例
  • sum% - 上面每一行 flat% 的总和
  • cum - 当前函数本身加上其调用函数的总耗时
  • cum% - cum 占 cpu 总时间的比例

说明:flat == cum 函数中没有调用其他函数;flat == 0 函数中只有其他函数的调用

内存问题排查过程

  1. 内存占用检查 go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
  2. 查看 top 视图与 source 视图(搜索框搜索对应 list 命令
  3. 排查掉 mouse.go
  4. 转到 SAMPLE/alloc_space,排查掉 dog.go

SAMPLE 采样分类如下

  • alloc_objects 程序累积申请的对象数
  • alloc_space 程序累积申请的内存大小
  • inuse_objects 程序当前持有的对象数
  • inuse_space 程序当前占用的内存大小

协程问题排查过程

  1. go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
  2. 转到 VIEW/Flame Graph,查看火焰图
  3. 由上到下表示调用顺序
  4. 每一块代表一个函数,越长表示占用 cpu 时间越长
  5. 火焰图是动态的,支持点击色块进行分析
  6. 排查掉 wolf.go

锁与阻塞问题排查过程

  1. go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
  2. 解锁时间过长
  3. 排查掉 wolf.Howl
  4. go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
  5. 阻塞时间过长
  6. 排查掉 cat.go