关于 slice
性能优化,大家已经有最基本的共识,即在创建 slice
指定初始化的 容量大小 比动态分配性能会有3倍左右的性能提升。不过一定要注意,是 容量大小(cap)不是长度(len)
例如声明一个长度为10个容量大小的数组,一定要要写指定 cap
大小:
// make([]T, len, cap)
// 正确做法
ages := make([]int, 0, 10)
// 不好的做法
ages := make([]int, 10) // 第三个参数 cap 容量大小被忽略
如果你在初始化时未指定 cap
大小,例如: make([]int, 10)
忽略了第三个参数,那么这种方式和声明动态切片的方式 var ages []int
性能上压根儿就没多大提升。我们不妨使用 benchmark
来直观感受下:
首先封装三个函数,模拟动态初始化,固定长度和固定容量大小三种使用方式,分别插入10000个整型数字。
func dynamicArr() {
var tmpArr []int
num := 10000
for i := 0; i < num; i++ {
tmpArr = append(tmpArr, i)
}
}
func fixedLenArr() {
tmpArr := make([]int, 10000)
num := 10000
for i := 0; i < num; i++ {
tmpArr = append(tmpArr, i)
}
}
func fixedCapArr() {
tmpArr := make([]int, 0, 10000)
num := 10000
for i := 0; i < num; i++ {
tmpArr = append(tmpArr, i)
}
}
接下来我们使用基准测试压测一下:
// 动态分配空间
func BenchmarkDynamicArr(b *testing.B) {
for i := 0; i < b.N; i++ {
dynamicArr()
}
}
// 不指定cap大小
func BenchmarkFixedLenArr(b *testing.B) {
for i := 0; i < b.N; i++ {
fixedLenArr()
}
}
// 指定cap大小
func BenchmarkFixedCapArr(b *testing.B) {
for i := 0; i < b.N; i++ {
fixedCapArr()
}
}
同样三个基准函数对应三个不同初始化测试函数:
go test -bench=. -benchmem
BenchmarkDynamicArr-4 20336 55221 ns/op 386296 B/op 20 allocs/op
BenchmarkFixedLenArr-4 19614 59569 ns/op 507904 B/op 4 allocs/op
BenchmarkFixedCapArr-4 81760 14120 ns/op 81920 B/op 1 allocs/op
如上结果对比一目了然,初始化时不管是指定长度还是容量大小,整体内存分配次数确实都减小了很多,分别是 4 次和1次,但平均的运行时间,只有在指定容量cap时,才会有大的提升。只指定长度len后的运行效率反而比动态分配更慢了,但基本上和动态初始化在同一水平线上。
结论:在创建slice时,如果能够预知创建元素数量,初始化尽量指定其容量大小,且一定要注意是容量cap大小。