【go代码优化】slice使用

二维码
| Sep 05, 2020 | 原创

go代码优化 slice

关于 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大小