这是我参与「第五届青训营 」笔记创作活动的第2天
概述
- 介绍编码规范,帮助大家写出高质量程序
- 介绍 Go 语言的性能优化建议,分析对比不同方式对性能的影响和背后的原理
- 讲解常用性能分析工具 pprof 的使用和工作原理,熟悉排查程序性能问题的基本流程
- 分析性能调优实际案例,介绍实际性能调优时的工作内容
实践准备
- 尝试使用 test 命令,编写并运行简单测试 go.dev/doc/tutoria…
- 尝试使用 -bench 参数,对编写的函数进行性能测试,pkg.go.dev/testing#hdr…
课程
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{}