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

126 阅读7分钟

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

1.高质量编程

1.1高质量简介

高质量-编写的代码达到正确可靠、简洁清晰的目标

编程原则:

  • 简单性,消除多余复杂性
  • 可读性,确保代码可读是编写可维护代码的条件
  • 生产力,提高团队工作效率

1.2编码规范

编码规范包括以下几个内容:

  1. 代码格式
  2. 注释
  3. 命名规范
  4. 控制流程
  5. 错误和异常处理

1.2.1编码规范-代码格式

  • gofmt:Go语言官方提供工具,可以自动格式化Go语言代码为官方统一风格
  • goimports:Go语言官方提供工具(=gofmt+依赖包管理),自动删除依赖包的引用、将依赖包按字母排序并分类

image.png

1.2.2编码规范-注释

注释作用:

  • 解释代码作用
  • 解释代码如何做
  • 解释代码实现原因
  • 解释代码什么情况会出错

1.3命名规范

  1. 缩略词要大写,但是当它位于变量开头并不需要导出时,使用全小写
  • 用ServerHTTP而不是ServerHttp
  • 用XMLHTTPRequest或者xmlHTTPRequest
  1. 变量距离其被使用的地方越远,则需要携带更多的上下文信息
  2. function
  • 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
  • 函数名要尽量简洁
  • 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义
  • 当名为foo的包某个函数返回类型T时 (T并不是Foo),可以在函数名中加入类型信息
  1. package
  • 只由小写字母组成。不包含大写字母和下划线等字符
  • 简短并包含一定的上下文信息。例如schema、task等
  • 不要与标准库同名。例如不要使用sync或strings
  • 不使用常用变量名作为包名。例如使用bufio而不是buf
  • 使用单数而不是复数。例如使用encoding而不是encodings
  • 谨慎地使用缩写。例如使用fmt在不破坏上下文的情况下比format更加简短

1.4控制流程

  1. 避免嵌套,保持正常流程清晰

如果if-else两个分支中都包含return语句,则可以去除冗余的else

  1. 尽量保持正常代码路径为最小缩进

优先处理错误情况/特殊情况,尽早返回或继续循环来减少嵌套

1.5错误和异常处理

  1. 简单错误
  • 简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
  • 优先使用errors.New来创建匿名变量来直接表示简单错误
  • 如果有格式化的需求,使用fmt.Errorf
  1. 错误的Wrap和Unwrap
  • 错误的Wrap实际上是提供了一个error嵌套另一error的能力,从而生成一个error的跟踪链
  • 在fmt.Errorf中使用%w关键字来将一个错误关联至错误链中
  1. 错误判定
  • 判定一个错误是否为特定错误,使用errors.Is
  • 不同于使用==,使用该方法可以判定错误链上的所有错误是否含有特定的错误
  • 在错误链上获取特定种类的错误,使用errors.As
  1. panic
  • 不建议在业务代码中使用panic
  • 调用函数不包含recover会造成程序崩溃
  • 若问题可以被屏蔽或解决,建议使用error代替panic
  • 当程序启动阶段发生不可逆转的错误时可以在init或main函数中使用panic
  1. recover
  • recover只能在被defer的函数中使用
  • 嵌套无法生效
  • 只在当前goroutine生效
  • defer的语句是后进先出
  • 如果需要更多的上下文信息,可以recover后在log中记录当前的调用栈

1.6性能优化建议

1.6.1性能优化建议-Benchmark

  • 性能的好坏需要用实际的数据量来衡量
  • Go语言中提供了支持基准性能测试的benchmark工具

使用命令go test -bench=. -benchmem可以进行评估程序的性能,进入到对应的目录,打开cmd运行该命令,运行结果如下

image.png 插语: 当我运行该命令go test -bench=. -benchmem的时候,出现了如下图的错误 image.png 解决办法: 通过go env命令查看发现GO111MODULE没有关闭 image.png

我们通过set GO111MODULE=off命令将它关闭,问题就解决了 image.png

1.6.2性能优化建议-Slice

  • slice预分配内存

尽量在使用make()初始化切片时提供容量信息,特别是在追加切片时,指定容量信息后,性能会提高

image.png

1.6.3性能优化建议-map

  • map预分配内存 初始化map时提供容量信息,指定容量信息后,性能会提高

image.png

1.6.4性能优化建议-字符串处理

  • 字符串三种拼接方式
  1. 直接使用加号+
  2. strings.Builder
  3. bytes.Buffer 推荐使用strings.Builder进行拼接字符串,因为它最快,其中bytes.Buffer较快,直接使用加号+最慢

1.6.5性能优化建议-空结构体

空结构体struct{}实例不占据任何内存空间,可作为占位符使用,可用来实现set(Go中不支持set实现),在集合场景下,只需要用到map的键而不使用值

1.6.6性能优化建议-atomic包

多线程编程中怎么保证线程安全,可以使用atomic包,锁的实现是通过操作系统来实现,属于系统调用,atomic 操作是通过硬件实现的,效率比锁高很多

2.性能调优实战

2.1性能调优原则

  • 以数据为依据而不是猜测(因为实际代码运行环境可能和预想不一样)
  • 要定位最大瓶颈而不是细枝末节
  • 不要过早优化(系统一直在迭代,况且系统早期用户不多,可以等系统出现性能问题在做优化)
  • 不要过度优化

2.2性能分析工具-pprof工具

2.2.1pprof工具概念

pprof是用于可视化和分析性能分析数据的工具(我们希望知道应用在什么地方耗费了多少 CPU、Memory)

2.2.2pprof工具-排查实战

  1. 启动如下图程序

image.png

  1. 启动完成后在本地浏览器(http://localhost:6060/debug/pprof/) 查看性能指标

image.png

  1. CPU性能

使用命令—go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" 在命令行窗口进行分析,如下图 image.png

继续输入top命令,如下图 image.png

  • flat 当前函数本身的执行耗时
  • flat% flat占CPU总时间的比例
  • sum% 上面每一行的flat%总和
  • cum 指当前函数本身加上其调用函数的总耗时
  • cum% cum占CPU总时间的比例
  • 当Flat=Cum时,意思是函数中没有调用其他函数,当Flat=0时,意思是函数中只有其他函数的调用

使用list+函数名命令可以查看具体问题代码,如下图(可以看到for循环所占时间最多) image.png

  1. Heap-堆内存

使用命令——go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap" ,出现如下图问题(说明没有安装Graphviz并没有配置环境变量)

image.png image.png

解决问题: 下载Graphviz,安装过程中选择如下图的添加系统PATH image.png

安装好以后Graphviz,继续在终端中使用命令go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap" ,如下图所示(下图说明mouse程序最占内存)

1676580048563.jpg

  1. goroutine-协程

goroutine泄露也会导致内存泄露,使用——go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine" 命令查看

image.png

我们还可以选择使用火焰图进行分析性能,如下图

image.png

  • 由上到下表示调用顺序
  • 每一块代表一个函数,越长代表占用 CPU 的时间更长
  • 火焰图是动态的,支持点击块进行分析
  1. mutex-锁

使用命令——go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"

image.png

  1. block-阻塞

使用命令——go tool pprof -http=:8080 "[http://localhost:6060/debug/pprof/block"

2.2.3pprof工具-采样过程和原理