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

163 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记

课程导学链接

【Go 语言原理与实践学习资料(上)】第三届字节跳动青训营-后端专场 - 掘金

课程项目地址

wolfogre/go-pprof-practice: go pprof practice. (github.com)

课程PPT

高质量编程与性能调优实战.pptx - 飞书云文档 (feishu.cn)

课程概要

  • 介绍编码规范,帮助大家写出高质量程序

  • 介绍 Go 语言的性能优化建议,分析对比不同方式对性能的影响和背后的原理

  • 讲解常用性能分析工具 pprof 的使用和工作原理,熟悉排查程序性能问题的基本流程

  • 分析性能调优实际案例,介绍实际性能调优时的工作内容

image.png

高质量编程

简介

编写的代码达到正确可靠、简洁清晰的目标,包括各种边界条件是否考虑完备、异常情况处理、稳定性、保证易读易维护

编程原则

image.png

编码规范

  1. 代码格式:gofmt和goimports使用

  2. 注释:应让代码成为最好的注释,注释应该提供代码未表达出的上下文信息

    • 解释代码作用:公共符号的注释
    • 解释代码实现过程
    • 解释代码实现的原因:解释代码的外部因素并提供额外上下文
    • 解释代码什么情况会出错:性能隐患、输入限制条件,让使用者无需了解实现细节
  3. 命名规范:降低代码阅读的成本,结合上下文信息设计简洁清晰的名称

    • variable:简洁;缩略词全大写,但当其位于变量开头且不需导出时使用全小写;距离使用的位置越远,越需携带相关信息
    • function:尽量简短;不携带包名信息;当名为foo 的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义;当名为foo的包某个函数返回类型T时(T并不是Foo),可以在函数名中加入类型信息
    • package:包名应全部为小写;如果含义明显,名称应缩写(如fmt);尽量使用一个词,如果需要两个词,不应该用下划线分隔,第二个词也不应该大写(strconv包就是一个例子);不要使用包用户可能也想使用的名称
  4. 控制流程

    • 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
    • 尽量保证正常代码缩进为最小缩进
  5. 错误和异常处理(error、panic和recover的使用)

    • 简单错误:仅出现一次的错误,且在其他地方不需要捕获该错误

      ​ 优先使用errors.New来创建匿名变量来直接表示简单错

      ​ 如果有格式化的需求,使用fmt.Errorf

    • 错误的Warp和Unwarp(go1.13后):

      ​ 错误的Wrap提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链

      ​ 可以在 fmt.Errorf中使用 %w 关键字来将一个错误关联至错误链中

    • 错误判定:

      ​ 判定一个错误是否为特定错误,使用errors.Is,不同于使用==,该方法可以判定错误链上的所有错误是否含有特定的错误

      ​ 在错误链上获取特定种类的错误,使用errors.As

    • panic:

      ​ 不建议在业务代码中使用panic

      ​ 调用函数不包含recover会造成程序崩溃

      ​ 若问题可以被屏蔽或解决,建议使用error代替panic

      ​ 当程序启动阶段发生不可逆转的错误时,可以在 init或main函数中使用panic

    • recover:

      ​ recover 只能在当前goroutine的被defer 的函数中生效,嵌套无法生效

      ​ defer的语句是后进先出

      ​ 如果需要更多的上下文信息,可以recover后在 log 中记录当前的调用栈

性能优化建议

  1. 简介

    • 性能优化的前提是满足正确可靠、简洁清晰等质量因素
    • 性能优化是综合评估,有时候时间效率和空间效率可能对立
    • 针对GO语言特性,介绍Go相关的性能优化建议
  2. Benchmark:利用基准性能测试工具的测量数据衡量性能

    image.png

  3. Slice:

    • 创建slice时根据预计容量指定cap容量以减少自动扩容次数

    • 大内存未释放陷阱:在已有切片基础上创建切片,不会创建新的底层数组场景

      ​ 原切片较大,代码在原切片基础上新建小切片

      ​ 原底层数组在内存中有引用,得不到释放

      ​ 可使用copy替代 re-slice

  4. Map:同Slice应根据1预计容量来预分配内存

  5. string处理:使用+拼接性能最差,strings.Builder与bytes.Buffer 相近,strings.Builder更快

    • 字符串在Go语言中是不可变类型,占用内存大小是固定的;使用+每次都会重新分配内存
    • strings.Builder,bytes.Buffer底层都是[]byte数组,内存扩容策略,不需要每次拼接重新分配内存
    • bytes.Buffer转化为字符串时重新申请了一块空间,strings.Builder 直接将底层的[]byte 转换成了字符串类型返回
    • 能预估最终长度时,能进一步提升效率
  6. 空结构体

    • 空结构体struct的实例不占据任何的内存空间
    • 可作为各种场景下的占位符使用,如实现set、channel接收发送数据
  7. atomic包

    • 锁的实现是通过操作系统来实现,属于系统调用;atomic操作是通过硬件实现,效率比锁高
    • sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
    • 对于非数值操作,可以使用atomic.Value,能承载一个interface{}
  8. 总结

    • 避免常见的性能陷阱可以保证大部分程序的性能
    • 普通应用代码,不要一味地追求程序的性能
    • 越高级的性能优化手段越容易出现问题
    • 在满足正确可靠、简洁清晰的质量要求的前提下提高程序性能

性能调优实战

性能调优原则

  • 要依靠数据不是猜测
  • 要定位最大瓶颈而不是细枝末节
  • 不要过早和过度优化

pprof

  1. 功能介绍

    image.png

  2. 排查实战:结合导学链接+PPT进行操作即可;可以阅读下面的博客获得更详细的过程

    golang pprof 实战 | Wolfogre's Blog

    ​ 注意从GitHub中clone下来的项目中没有go.mod文件的,而go1.18强制使用module来管理项目,需要我们自己生成go.mod文件

    $ go mod init github.com/wolfogre/go-pprof-practice
    $ go mod tidy