高质量编程-性能调优
前言:
高质量的代码可以完成功能,但我们还需要考虑尽可能的提升性能,节省资源成本。本文结合Go语言展开。
性能优化建议
介绍:
- 性能表现需要实际的数据来支持衡量
- 性能优化是综合的,有时候时间和空间是不能兼顾的,所谓的时间换空间or空间换时间
Benchmark
性能的表现需要数据来说话,Go语言提供了支持基准性能测试的benchmark
工具。
使用go test -bench=, -benchmem命名即可调用
// 一个例子
// from fib.go
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n - 1) + Fib(n - 2)
}
// from fib_test.go
func BenchmarkFib10(b *testing.B) {
// run the Fib funciton b.N times
for n := 0; n < b.N; n++ {
Fib(10
}
}
Slice:
尽可能在使用make()初始化的时候就提供容量信息,获得更好的性能。
当append之后的len<=cap,将会直接利用底层数组剩余的容量空间。但当len>cap时,则会分配一块更大的区域来容纳新的底层数组。因此,为了避免内存发生拷贝,最好预先设置cap。
究其原因:
- 切片本质是一个数组片段的描述包括数字指针、片段的长度以及片段的容量
- 切片操作并不复制切片指向的元素
- 创建一个新的切片会复用原来切片的底层数组
另一个陷阱--大内存未释放:
有一种情况,原切片由大量元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量的空间,得不到释放。我们可以使用copy代替re-slice
//re-slice
func GetLastSlice(origin []int) []int {
return origin[len(origin)-2:]
}
//copy
func GetLastCopy(origin []int) []int {
result := make([]int,2)
copy(result,origin[len(origin)-2:])
return
}
可以使用go test -run=. -v来查看性能
Map预分配内存:
与slice一样,建议根据实际需求提前预估好需要的空间
原因:
- 不断向map中添加元素的操作会触发map扩容
- 提前分配好空间可以减少内存拷贝和Rehash(重新的分配并加入新的桶内)的消耗
字符串处理:
字符产拼接建议使用strings.Builder,使用+的拼接性能最差。分析:
- 字符串在Go语言中是不可变类型,占用内存大小是固定的
- 使用
+
每次都会重新分配内存 strings.Builder, bytes.Buffer
底层都是[]byte
数组- 内存扩容策略,不需要每次拼接重新分配内存
// 例子
func StrBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
空结构体:
空结构体是节省内存空间的一个手段。
- 空结构体
struct{}
实例不占据任何的内存空间 - 可作为各种场景下的占位符使用
- 节省资源
- 空结构体本身具备很强的语义,不需要任何值,仅作为占位符
// 一个例子
func EmptyStructMap(n int) {
m := make(map[int]struct{})
for i := 0; i < n; i++ {
m[i] = struct{}{}
}
}
func BoolMap(n int){
m := make(map[int]bool)
for i := 0; i < n; i++ {
m[i] = false
}
}
atomic包:
原子性:一个或多个操作在CPU的执行过程中不被中断的特性,称为原子性。这些操作对外表现成一个不可分割的整体,他们要么都执行,要么都不执行,外界不会看到他们只执行到一半的状态。
原子操作:进行过程中不能被中断的操作,原子操作由底层硬件支持,而锁则是由操作系统提供的API实现,若实现相同的功能,前者通常会更有效率
Go语言提供的原子操作都是非入侵式的,由标准库中sync/aotomic
中的众多函数代表。
提供了 AddXXX、CompareAndSwapXXX、SwapXXX、LoadXXX、StoreXXX 等方法。
性能调优实战:
调优原则:
- 依靠数据而非猜测
- 找到决速反应,而非细枝末节
- 不要过早和过度的优化
性能分析工具pprof:
炸弹程序:wolfogre/go-pprof-practice: go pprof practice. (github.com)
功能简介
实战排查
运行炸弹程序后打开浏览器访问 http://localhost:6060/debug/pprof/即可查看数据。
排查实战:
详细文档:golang pprof 实战 | Wolfogre's Blog(已经写的很好了,我又何必画蛇添足,实际操作一遍,自然就会了)
性能调优案例
业务服务优化:
业务服务一般指直接提供功能的程序。
基本概念:
- 服务:能单独部署,承载一定功能的程序
- 依赖:Service A的功能实现依赖,Service B的响应结果,称为Service A依赖Service B
- 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
- 基础库:公共的工具包、中间件
流程:
通用的流程,也可以适用其它场景
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈(Go中主要使用pprof)
- 使用库不规范
- 高并发场景优化不足
- 重点优化项改造
- 正确性是基础
- 优化效果验证
- 重复测压验证
- 上线评估优化效果
永不满足,未完待续..
空空如也!