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

123 阅读4分钟

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

概述

  • 介绍编码规范,帮助大家写出高质量程序
  • 介绍 Go 语言的性能优化建议,分析对比不同方式对性能的影响和背后的原理
  • 讲解常用性能分析工具 pprof 的使用和工作原理,熟悉排查程序性能问题的基本流程
  • 分析性能调优实际案例,介绍实际性能调优时的工作内容

image-20230117191425337

实践准备

课程

1.高质量编程

代码正确可靠、简洁清晰

1.2.1 编码规范

推荐使用gofmt自动格式化代码,他能将Go语言格式化为官方统一风格,常见IDE中都有

1.2.2 注释

代码的作用、代码如何做的、代码实现的原因、代码什么时候会出错

1.2.3 命名规范

两个原则:

1.核心目标是降低阅读代码的成本

2.重点考虑上下文信息,设计简介清晰的名称

好的命名就像一个好的笑话。如果你必须解释它,那就不好笑了

1.2.4 控制流程

优先对异常情况进行特殊判断,如果遇到错误直接返回,等所有异常情况处理完毕之后,再去处理正常情况

尽量避免出现复杂的嵌套分支,大多数的问题故障都出现在复杂的条件语句和循环语句中,如下图代码就很棒

1.2.5 错误和异常处理

1.3.1 性能优化建议——工具使用

Go提供支持基准性能测试的benchmark工具

go test -bench=. -benchmem

执行后的结果非常丰富,包括总共执行次数、每次执行花费、每次执行申请的内存大小、申请内存的次数等,如图:

具体使用过程

1.基准测试代码文件必须是 _test.go(自己创建)结尾,和单元测试一样

2.基准测试的函数以Benchmark开头

3.参数须为 *testing.B

4.b.ResetTimer是重置计时器,这样可以避免for循环之前的初始化代码的干扰

5.b.N是基准测试框架提供的,Go会根据系统情况生成

运行benchmark基准测试也要用到 go test 命令,不过我们后面需要加上-bench=参数,接受一个表达式作为参数,匹配基准测试的函数,"."一个点表示运行所有的基准测试

一个具体的测试函数性能的代码,可以直接点击运行来运行or命令行实现

疑问:为啥在goland中的终端无法执行go test -bench=. -benchmem呢

1.3.2 性能优化建议-Slice

使用切片时,尽可能在make()初始化时提供容量信息,以避免多次扩容切片,如右图

测试这两方法时,可以看到明显的性能对比

1.3.3 Map

map预分配内存

与切片类似,不断向map中添加元素的操作会触发map的扩容,所以提前分配好内存可以减少内存拷贝和Rehash的消耗

1.3.4 字符串处理

字符串拼接的三种方法对比

方法1:直接用符号+来进行字符串拼接

方法2(推荐使用):使用strings.Builder

方法3(更快):使用byte.Buffer

原理:

字符串在Go语言中是不可变类型,占用内存大小是固定的。所以每次使用+会重新分配内存,而strings.Builder和byte.Buffer底层都是[]byte数组,内容扩容策略,不需要每次拼接重新分配内存

进一步提升

字符串拼接和切片一样,在知道字符串长度的情况下,可以进一步提高性能,使用Grow方法来预分配内存

于此同时,我们可以发现,strings.bulider只分配了一次内存,byte.buffer分配了两次(因为它转化为字符串的时候用buf.String(),重新分配了一块内存,即最初分配一次,最后分配一次)

1.3.5 空结构体

空结构体不占用任何内存空间

1.3.6 atomic包——适合多线程编程

用这个包会比直接用加锁的性能好很多

关于atomic包

锁的实现是通过操作系统来实现,属于系统调用

atomic操作是通过硬件实现,效率比锁要高

sync.Mutex应该用于保护一段逻辑,不仅仅用于保护一个变量

对于非数值操作,可以使用atomic.Value,能够承载一个interface{}