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

98 阅读6分钟

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

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天,主要记录相关的知识点。

本堂课重点内容

  • 高质量编程
  • 性能调优
  • 单元测试
  • 项目实战

高质量编程

对于编码的质量问题,有非常多的评价标准,但普遍来说,一个高质量的代码通常都具有如下特点:

  • 正确可靠,边界考虑完备
  • 异常处理、稳定性保证
  • 可读性强,可维护

简单而言,需要保证代码具有 简单性、可读性与生产效率保证 的特点。

注释

注释需要给出代码未明确给出的上下文信息,例如:

  • 做什么
  • 怎么做
  • 为什么这么做
  • 错误的处理

对于公共的符号,需要给出注释。

代码格式

可以使用一些工具来帮助实现优美的代码格式:

  • gofmtgofmt
  • goimportsgoimports

命名规范

有一些公共的,统一的变量命名规范,例如:

  • 缩略词全大写,除非其位于变量开头且不需要导出时(全小写)
  • 变量距离其被使用的地方越远,需要携带更多的上下文信息,比如全局变量

对于函数,也有一些命名规范:

  • 函数名尽量简短
  • 函数名不需要携带包名的上下文信息
  • 当包名(比如 foofoo)与返回类型(比如 FooFoo )一致时,可以省略类型信息避免歧义
  • 当包名(比如 foofoo )与返回类型(比如 BoolBool )不一致时,可以添加类型信息明确含义

对于包,也有一些命名规范可以参考:

  • 只有小写字母构成,不包含大写字母、下换线等符号
  • 简短,并包含一定的上下文信息
  • 避免与标准库同名
  • 避免与经常使用的变量重名,比如 bufio/bufbufio/buf
  • 使用单数而非负数
  • 缩写谨慎使用,保证含义可以被广泛接受

流程控制

在实际的流程控制结构中,我们可以:

  • 避免不必要的流程嵌套,保证流程清晰
  • 优先处理错误或特殊情况,尽早返回或减少循环次数减少嵌套行为的发生
  • 保持代码使用最小缩进

错误与异常处理

在错误处理中,对于一个仅发生一次,且其他地方不需要捕获该错误的情况:

  • 优先考虑使用 errors.Newerrors.New
  • 需要格式化,使用 fmt.Errorffmt.Errorf

对于复杂情况,我们可以形成一个错误的跟踪链,通过跟踪链来找到错误:

  • 使用 WrapWrap 来让一个 errorerror 嵌套另外的 errorerror
  • fmt.Errorffmt.Errorf 中使用 %w\%w 来将一个错误关联到一个错误链中

有时,我们需要判断一个错误是否是指定类型的错误,这时候可以用 errors.iserrors.is ,使用该方法可以同时判断错误链上的所有错误是否包含该错误。

还可以用 errors.Aserrors.As 可以查看特定种类错误的内容。

另外,不建议在业务中使用 panicpanic ,因为一旦使用 panicpanic 而不 recoverrecover 的话,那么程序会直接崩溃。

对于 recoverrecover ,其:

  • 只能在当前的 goroutinegoroutine 生效
  • 只能在被 deferdefer 的函数中使用
  • 嵌套无法生效
  • deferdefer 后进先出

性能调优

在实际业务中,性能调优也是很重要的一个部分,下面将简单讲述其中的几个技巧。

预分配

SliceSliceGoGo 中非常常用的数据类型,其自动分配扩容内存的特性在很多时候帮助简化了实际的编码,但是也因为这个机制,可能会导致不必要的扩容问题。

为了减少因为 SliceSlice 自动扩容分配内存,我们可以在 makemake 时就设置好容量。类似的 MapMap 也可以进行预分配。

使用 copy 代替 re-slice

对于一个切片,再进行切片操作,不会释放原有的内存空间,这就意味着原来的容量很大的切片(底层数组)并不会得到释放。

尽量使用 strings.Builder 来拼接字符串

常用的拼接字符串的方法有 ++strings.Builderstrings.Builderbytes.Bufferbytes.Buffer ,在这三种方法中,使用 ++ 会导致每次拼接都会重新分配内存,导致运行速度慢; bytes.Bufferbytes.Buffer 会在最后返回时进行一次内存分配并返回字符串;而 strings.Builderstrings.Builder 是直接通过 []byte[]byte 来转换成字符串返回,不存在内存分配的过程,因此速度最快。

使用空结构体

一个空的结构体 struct{}struct\{\} 是不会占用内存空间的,因此,在某些场景下可以作为占位符使用。

比如:实现一个 SetSet

使用 atomic

使用 atomicatomic 包可以进行一些原子操作,是通过硬件实现的,因此具有很高的效率。

Pprof工具

为了实现良好的优化,我们可以使用辅助工具 PprofPprof 来帮助我们实现性能优化。

PprofPprof 总结起来可以被概括如下:

image.png

可以在浏览器中输入如下内容启动 PprofPprof

localhost:端口/debug/pprof

PprofPprof 可以对不同信息进行采样,一个通用的的语句为:

go tool pprof -http=:新端口号(一般写8080即可) "http://localhost:6060/debug/pprof/参数"

CPU

可以通过如下命令查看函数占用 CPUCPU 运行时间情况。

go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=秒数"

1673951034202.jpg

等待运行结束,此时会进入 PprofPprof 终端,输入 toptop 查看结果

image.png

参数解释:

  • flatflat 当前函数本身的执行耗时
  • flat%flat\%flatflatCPUCPU 运行时间的百分比
  • sum%sum\% 从上往下的 flat%flat\% 之和
  • cumcum 当前函数本身加上其调用函数的总耗时
  • cum%cum\%cumcumCPUCPU 运行时间的百分比

flatflat == cumcum 时,说明当前函数不会调用其他函数。

faltfalt == 00 ,说明当前函数自身不产生消耗。

由于上面发现 EatEat 方法占用了大量时间,我们可以使用 listlist 来列出该函数。

list Eat

image.png

我们还可以使用 webweb 可视化。

web

image.png

Heap

使用如下命令:

go tool pprof -http=:8081 "http://localhost:6060/debug/pprof/heap"

1673951825149.jpg

我们可以在这里调整要查看的视图:

image.png

使用 TopTop 可以排序展示。

使用 SourceSource 可以快速定位到问题:

image.png

SampleSample 中可以指定采样:

image.png

  • alloc_objectsalloc\_objects 程序累计申请的对象数
  • alloc_spacealloc\_space 程序累计申请的内存大小
  • inuse_objectsinuse\_objects 程序当前持有对象数
  • inuse_spaceinuse\_space 当前占用的内存大小

Goroutine

使用如下命令:

go tool pprof -http=:8081 "http://localhost:6060/debug/pprof/goroutine"

类似,使用 SourceSource 定位。

Mutex

使用如下命令:

go tool pprof -http=:8081 "http://localhost:6060/debug/pprof/mutex"

image.png

类似,使用 SourceSource 定位。

Block

使用如下命令:

go tool pprof -http=:8081 "http://localhost:6060/debug/pprof/block"

类似,使用 SourceSource 定位。

个人总结

本次课程主要学习了:

  • 高质量编程
  • 性能调优
  • PprofPprof