这是我参与「第五届青训营 」伴学笔记创作活动的第3天
一、课程内容简介
- 如何编写更简洁清晰的代码
- 常用Go语言程序优化手段
- 熟悉Go程序性能分析工具
- 了解工程中性能优化的原则和流程
二、详细知识展示
1. 高质量编程:编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性的保证
- 是否易读易维护
实际应用场景千变万化,各种语言的特性和语法各不相同,但是高质量编程遵循的原则是相通的
- 简单性:消除“多余的复杂性”,以简单清晰的逻辑编写代码;不理解的代码无法修复改进;
- 可读性:代码是给别人看的,而不是机器;编写可维护代码的第一步是确保代码可读;
- 生产力:团队整体工作效率非常重要;
如何编写高质量代码?
代码格式、注释、命名规范、控制流程、错误和异常处理
- 推荐使用gofmt自动格式化代码,这是go语言官方提供的工具,能够自动格式化go语言代码为官方统一风格,常见的IDE都支持方便的配置;goimports也是go语言官方提供的工具,实际等于gofmt加上依赖包管理,自动增删依赖的包引用、将依赖包按字母排序并分类。
- 注释应该解释代码作用、解释代码如何做的、解释代码实现原因、什么情况下会出错。代码就是最好的注释,注释应该提供代码没有表达出的(额外的)上下文信息。公共符号始终要注释,但不要说废话。
- 变量命名的时候简洁胜于冗长;缩略词需要全大写,但当其位于变量开头且不需要导出时,使用全小写,例如要使用
ServerHTTP而不是ServerHttp,使用xmlHTTPRequest而不是XMLHTTPRequest;变量距离其被使用的地方越远,则需要携带越多的上下文信息,全局变量在其名字中需要更多的上下文信息,使得在不同的地方可以被轻易辨认。 - 函数命名的时候函数名不携带包名的上下文信息,因为调用的时候要包.函数,成对出现;函数名要尽量简短。
- 控制流程中需要避免if else的嵌套,保证正常的流程清晰,比如两个分支都包含return语句,则可以去除冗余的else。相对复杂的流程来说,尽量保证正常代码路径为最小缩进,优先处理错误和特殊情况,尽早返回或继续循环来减少嵌套。正常流程代码沿着屏幕向下移动,避免复杂的嵌套分支,因为故障问题大多出现在复杂的条件语句和循环语句中。
- 错误和异常处理:
有关错误链,一个错误嵌套另一个错误,每一层的调用方都可以补充上下文信息方便跟踪排查问题。
fmt.Errorf中使用%w关键字来将一个错误关联至错误链中。错误判定用errors.Is来判断错误链上是否有相关错误。还可以用errors.As来获取特定错误的内容。 panic用于真正异常的情况,是当程序启动阶段发生不可逆转的错误时,可以在init或者main中使用panic,不建议在业务代码中使用panic,这样会导致程序崩溃。 recover只能在defer的函数中使用,嵌套无法生效,而且只在当前的goroutine中生效。recover最常用的是在log中记录当前的调用栈信息。
2、性能优化建议
- 性能优化的前提是满足正确可靠、简洁清晰等质量因素
- 性能优化是综合评估,有时候时间效率和空间效率可能对立
- 针对Go语言特性,介绍Go相关的性能优化建议
benchmark工具是Go自带的一种测试方法,使用
go test -bench=. -benchmen来执行写好的test文件。上图的数据从左到右分别表示测试函数名、一共执行多少次、每次执行花费的时间、每次执行申请多大内存、每次执行申请几次内存。
slice预分配内存:尽可能在使用make()初始化切片时提供容量信息,能够减少底层内存分配次数,实际效果影响很明显。原因在于slice的定义和扩容的操作。map也有一样的预分配,不断向map中添加元素的操作会触发map扩容,提前预分配好空间可以减少内存拷贝和rehash的消耗。
字符串处理:这里有三种拼接字符串的不同方式直接+=、使用strings.Builder、bytes.Buffer
因为string类型在Go中是不可变类型,占用的内存大小是固定的,每次+都会重新分配内存。strings.Builder,bytes.Buffer底层都是[ ]byte数组,不需要每次拼接都分配内存。如果想进一步优化,string也有类似于slice内存预分配的手段
builder.Grow或者buf.Grow,相当于提前分配好string的长度,设计内存操作更少,比原先的速度更快
空结构体作为空间优化的一种方法,struct{}不占用任何内存空间,可以作为各种场景下的占位符使用,做到节省资源。空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符。例如
多线程性能优化中使用atomic会比直接使用mutex加锁速度快很多
3、性能调优实战
性能调优原则:
- 要依靠数据而不是猜测;
- 要定位最大瓶颈而不是细枝末节;
- 不要过早优化
- 不要过度优化
性能分析工具pprof :希望知道应用在什么地方耗费了多少CPU、Memory,用于可视化和分析性能的工具。
-
pprof功能简介:
-
搭建pprof实践项目跟随视频走一遍pprof的各项功能和操作,大致了解如何使用。真的真的很好用,很容易理解很容易上手!
pprof采样过程和原理
- CPU采样:采样对象是函数调用和它们占用的时间。采样率是100次/秒,固定值。采样时间是从手动启动到手动结束;
- Heap堆内存:采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量。采样率是512KB记录一次。采样时间是从程序运行开始到采样时。指标就是上面看过的四个;
- Goroutine协程:记录所有用户发起且在运行中的goroutine的runtime.main的调用栈的信息。
- Block阻塞和Mutex锁:阻塞是采样阻塞操作的次数和耗时,只有阻塞耗时超过阈值的才会被记录;锁竞争是采样争抢锁的次数和耗时,只记录固定比例的锁操作。
三、学习感受
第三节课的学习内容又比前两节课多了很多(视频时长太长啦)。不过今天这个老师讲的逻辑很清楚,语言表达很简洁,在讲解代码操作和概念的时候更能听得懂,今天学会了Go编码规范、性能调优的一些知识,对pprof工具进行重点的尝试。总之收获很多,期待以后的应用。
四、引用和参考
字节内部课《高质量编程与性能调优实战》