关于 slice 性能优化,大家已经有最基本的共识,即在创建 slice 指定初始化的 容量大小 比动态分配性能会有3倍左右的性能提升。不过一定要注意,是 容量大小(cap)不是长度(len)

    例如声明一个长度为10个容量大小的数组,一定要要写指定 cap 大小:

    1. // make([]T, len, cap)
    2. // 正确做法
    3. ages := make([]int, 0, 10)
    4. // 不好的做法
    5. ages := make([]int, 10) // 第三个参数 cap 容量大小被忽略

    如果你在初始化时未指定 cap 大小,例如: make([]int, 10) 忽略了第三个参数,那么这种方式和声明动态切片的方式 var ages []int 性能上压根儿就没多大提升。我们不妨使用 benchmark 来直观感受下:

    首先封装三个函数,模拟动态初始化,固定长度和固定容量大小三种使用方式,分别插入10000个整型数字。

    1. func dynamicArr() {
    2. var tmpArr []int
    3. num := 10000
    4. for i := 0; i < num; i++ {
    5. tmpArr = append(tmpArr, i)
    6. }
    7. }
    8. func fixedLenArr() {
    9. tmpArr := make([]int, 10000)
    10. num := 10000
    11. for i := 0; i < num; i++ {
    12. tmpArr = append(tmpArr, i)
    13. }
    14. }
    15. func fixedCapArr() {
    16. tmpArr := make([]int, 0, 10000)
    17. num := 10000
    18. for i := 0; i < num; i++ {
    19. tmpArr = append(tmpArr, i)
    20. }
    21. }

    接下来我们使用基准测试压测一下:

    1. // 动态分配空间
    2. func BenchmarkDynamicArr(b *testing.B) {
    3. for i := 0; i < b.N; i++ {
    4. dynamicArr()
    5. }
    6. }
    7. // 不指定cap大小
    8. func BenchmarkFixedLenArr(b *testing.B) {
    9. for i := 0; i < b.N; i++ {
    10. fixedLenArr()
    11. }
    12. }
    13. // 指定cap大小
    14. func BenchmarkFixedCapArr(b *testing.B) {
    15. for i := 0; i < b.N; i++ {
    16. fixedCapArr()
    17. }
    18. }

    同样三个基准函数对应三个不同初始化测试函数:

    1. go test -bench=. -benchmem
    2. BenchmarkDynamicArr-4 20336 55221 ns/op 386296 B/op 20 allocs/op
    3. BenchmarkFixedLenArr-4 19614 59569 ns/op 507904 B/op 4 allocs/op
    4. BenchmarkFixedCapArr-4 81760 14120 ns/op 81920 B/op 1 allocs/op

    如上结果对比一目了然,初始化时不管是指定长度还是容量大小,整体内存分配次数确实都减小了很多,分别是 4 次和1次,但平均的运行时间,只有在指定容量cap时,才会有大的提升。只指定长度len后的运行效率反而比动态分配更慢了,但基本上和动态初始化在同一水平线上。

    结论:在创建slice时,如果能够预知创建元素数量,初始化尽量指定其容量大小,且一定要注意是容量cap大小