这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
性能优化
- 性能优化的前提是满足正确可靠、简洁清晰等质量因素
- 性能优化是综合评估,有时候时间效率和空间效率可能对立
Benchmark
go test -bench=. -benchmem
- BenchmarkFib10是测试函数名,-8是GOMAXPROCS的值为8(cpu核数);
- 共执行1855870次,即b.N的值;
- 每次执行花费ns;
- 每次执行申请多大内存;
- 每次执行申请几次内存
Slice预分配内存
与数组不同的是,Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
-
尽可能在使用make()初始化切片时提供容量信息
data := make([ ]int, 0, size) -
切片本质是一个数组片段描述
- 包括数组指针
- 片段的长度
- 片段的容量(最大长度)
type slice struct { array unsafe.Pointer len int cap int }- 切片操作并不复制切片指向的元素
- 创建一个新的切片会复用原来切片的底层数组
-
另一个陷阱:大内存未释放
- 在已有切片基础上创建切片,不会创建新的底层数组
- 场景
- 原切片较大,代码在原切片的基础上新建小切片
- 原底层数组在内存中有引用,得不到释放
- 可使用copy替代re-slice
Map预分配内存
data := make(map[int]int, size)
- 不断向map中添加元素的操作会触发map的扩容
- 提前分配好空间可以减少内存拷贝和Rehash的消耗
- 建议根据实际需求提前估计好需要的空间
字符串处理
- 使用
strings.Builder
使用 + 拼接性能最差,strings.Builder,bytes.Buffer相近,strings.Buffer更快 - 分析
- 字符串在Go语言中是不可变类型,占用内存大小是固定的
- 使用 + 每次都会重新分配内存
strings.Builder,bytes.Buffer底层都是[]byte数组- 内存扩容策略,不需要每次拼接重新分配内存
- 实际
strings.Builder比strings.Buffer快,bytes.Buffer转化为字符串时重新申请了一块内存;
strings.Builder直接将底层的[]byte转换成了字符串类型返回 - 为了更进一步提升效率,
builder.Grow(n * len(str))预分配内存,bytes.Buffer也有。
空结构体
- 使用空结构体节省内存
m := make(map[int]struct{})- 空结构体struct{}实例不占任何内存空间
- 可作为各种场景下的占位符使用
- 节省资源
- 空结构体本身具有很强的语义,即这里不需要任何值,仅作为占位符
atomic包
多线程公用计数器,保证计数准确,线程安全
相较于锁机制,性能更高
- 锁的实现是通过操作系统来实现,属于系统调用
- atomic操作是通过硬件实现,效率比锁高
- sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
- 对于非数值操作,可以使用atomic.Value,能承载一个interface{}
性能分析工具 pprof
| allocs | 过去所有内存分配的采样 |
| block | 导致同步原语阻塞的堆栈跟踪 |
| cmdline | 当前程序的命令行调用 |
| goroutine | 所有当前goroutine的堆栈跟踪 |
| heap | 活动对象的内存分配的采样。在获取堆样本之前,可以指定gc GET参数来运行gc |
| metux | 争用互斥锁持有者的堆栈跟踪 |
| profile | CPU配置文件。您可以在seconds GET参数中指定持续时间。获取配置文件后,使用go tool pprof命令调查配置文件 |
| threadcreate | 导致创建新操作系统线程的堆栈跟踪 |
| trace | 当前程序的执行轨迹。您可以在seconds GET参数中指定持续时间。获取跟踪文件后,使用go tool trace命令调查跟踪 |
-
cpu
go tool pprof "http://localhost:6060/debug/pprof/profile?second=10"- 命令:topN
查看占用资源最多的函数Flat == Cum,函数中没有调用其他函数
Flat == 0,函数中只有其他函数的调用 - 命令:list
根据指定的正则表达式查找代码行 - 命令:web
调用关系可视化
- 命令:topN
-
heap - 堆内存
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"alloc_objects 程序累计申请的对象数 alloc_space 程序累计申请的内存大小 inuse_objects 程序当前持有的对象数 inuse_space 程序当前占用的内存大小 -
goroutine - 协程
goroutine泄漏也会导致内存泄漏
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"- 由上到下表示调用顺序
- 每一块表示一个函数,越长代表占用的cpu的时间越长
- 火焰图是动态的,支持点块进行分析