这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天。这篇笔记主要记录课堂上优化代码性能的方法。课程中介绍的优化方法主要针对Go语言。
重点内容
- 学习理解代码性能优化的基本思维与方法
- 学习使用一些性能优化工具
详细知识点
一、性能优化指南
简介
对代码的性能优化需要注意以下两点
- 性能优化的前提是代码正确可靠,逻辑简洁清晰
- 性能优化需要综合考量各方面因素的影响
性能测试工具——Benchmark
Benchmark工具是go语言提供的一种基准性能测试工具,该工具可以使用go test -bench=. -benchmem命令调用。下面借由网络上的一个测试结果对工具分析出的参数进行说明。
$ go test -bench ^Benchmark1Sort$ -run ^$
goos: linux
goarch: amd64
Benchmark1Sort-12 9252 110547 ns/op
PASS
ok _/home/mcaci/code/github.com/mcaci/dev-art/go-bench 1.053s
结果中,“Benchmark1Sort-12”是测试函数名-n,n为计算机内核数,“9252”代表一共执行的次数,“110547 ns/op”表示一次执行所花费的时间。
性能优化建议
Slice与Map
在Go语言中,Slice切片长度是可变的,这就代表着定义这类型数据时会需要进行内存预分配。因此在一些我们明确知道Slice所占空间大小的场合,我们可以在make()初始化Slice时提供容量信息从而减少无效内存占用,同时防止内存大小不足时扩容造成的性能损耗,下写法如下
data := make([]int,0)
data := make([]int,0,size)//提供了容量信息
map与Slice类似,都需要预分配内存,因此也是可以在初始化时提供容量信息,提升综合性能。
string
在处理字符串时,我们有许多处理方法。以下列出两种,即利用string和strings.Buffer进行字符串拼接
func Plus{n int,str string} string {
s := ""
for i := 0;i <n; i++ {
s +=str
}
return s
}
func Plus{n int,str string} string {
var builder strings.Builder
for i := 0;i <n; i++ {
builder.WriteString(str)
}
return builder.String()
}
对于字符串拼接操作,性能上有 strings.Buffer > bytes.Buffer >> string
分析:
- string在Go中属于不可变类型,内存大小固定,在上面的例子中,利用‘+’进行拼接时string的内存都会被重新分配,带来性能损耗
- strings.Buffer与bytes.Buffer底层都是[]byte数组,采用内存扩容策略,不需要每次拼接都重新分配
- 但对于strings.Buffer与bytes.Buffer,前者直接将byte数组转换为字符串,后者在转换是重新申请了一块内存,因此性能上前者略强于后者
空结构体与atomic包
在实际工程项目中,我们可以使用空结构体节省内存,理由如下
- 空结构体不占据任何内存空间
- 可作为占位符使用
atomic包的功能类似锁(sync.Mutex),但它的效率更高,理由如下
- 锁属于系统调用,而atomic操作通过硬件实现,效率更高
- 锁往往用于保护一段逻辑,而非一个变量。保护变量使用atomic更划算
- 对非数值操作,用atomic.value可以承载接口
二、性能优化工具
性能分析工具pprof
pprof是用于可视化分和分析性能分析数据的工具,可以帮助开发者了解模块的CPU与内存消耗
主要用途
- CPU Profiling:CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗CPU 周期时花费时间的位置
- Memory Profiling:内存分析,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏
- Block Profiling:阻塞分析,记录 goroutine 阻塞等待同步(包括定时器通道)的位置。阻塞分析对分析程序并发瓶颈非常有帮助。
- Mutex Profiling:互斥锁分析,报告互斥锁的竞争情况
个人总结
以上内容提供了对代码性能优化的方法以及对性能分析工具的基本介绍。事实上在优化性能方面,只要我们在编写程序时多想一想,避开编程中的性能陷阱,完成的代码基本上都能满足正常业务的要求。同时由于性能优化时会涉及较多的底层操作,而进行底层操作往往会导致bug产生,因此不应过度追求性能而放弃代码的正确性与可读性,不能犯舍本逐末的错误。