尽量使用cap参数创建切片和map

TrumanWong
9/14/2024
TrumanWong

切片和mapGo语言中重要数据类型,也是Gopher日常编码中最常使用的类型之一。在可以预估出元素容量的前提下,使用cap参数创建切片和map可以提升平均操作性能,减少或消除因频繁扩容带来的性能损耗。

使用cap参数创建切片

Golang切片底层原理一文中,我们可以看到,使用append对切片进行动态扩容时,重新分配底层数组并复制元素的操作代价还是比较大的,因此我们可以根据切片的使用场景对切片的容量规模进行预估,并在创建新切片时将预估出的切片容量数据以cap参数的形式传递给内置函数make

slice := make([]T, len, cap)

下面是一个使用cap参数和不使用cap参数的切片的性能基准测试代码:

package main

import "testing"

const size = 10000

func BenchmarkMakeSliceWithoutCap(b *testing.B) {
    for i := 0; i < b.N; i++ {
        arr := make([]int, 0)
        for j := 0; j < 10000; j++ {
            arr = append(arr, j)
        }
    }
}

func BenchmarkMakeSliceWithCap(b *testing.B) {
    for i := 0; i < b.N; i++ {
        arr := make([]int, 0, 10000)
        for j := 0; j < 10000; j++ {
            arr = append(arr, j)
        }
    }
}

以下是benchmark基准测试结果:

$ go test -benchmem -bench=. slice_test.go
goos: windows
goarch: amd64
cpu: 12th Gen Intel(R) Core(TM) i7-12700
BenchmarkMakeSliceWithoutCap-20            28789             43569 ns/op          357627 B/op         19 allocs/op
BenchmarkMakeSliceWithCap-20              107767             11192 ns/op           81920 B/op          1 allocs/op
PASS
ok      command-line-arguments  3.144s

从基准测试结果可以看到,使用带cap参数创建的切片进行append操作的平均性能(43569 ns/op)是不带cap参数的切片(11192 ns/op)的4倍左右,并且每操作平均仅需一次内存分配。因此,如果可以预估出切片底层数组需要承载的元素数量,强烈建议在创建切片时带上cap参数。

因此,如果可以预估出切片底层数组需要承载的元素数量,强烈建议在创建切片时带上cap参数。

使用cap参数创建map

哈希表与Go语言实现机制一文中,我们可以看到,如果创建map时没有创建足够多可以应付map使用场景的bucket,随着插入map元素数量的增多,map会频繁扩容,而这一过程将降低map的访问性能。因此,在创建map时应对map使用规模做出粗略的估算,并使用cap参数对map实例进行初始化。下面是使用cap参数与不使用map参数的map写性能基准测试代码:

package main

import "testing"

const mapSize = 10000

func BenchmarkMakMapWithoutCap(b *testing.B) {
    for i := 0; i < b.N; i++ {
        m := make(map[int]int)
        for j := 0; j < mapSize; j++ {
            m[i] = i
        }
    }
}

func BenchmarkMakMapWithCap(b *testing.B) {
    for i := 0; i < b.N; i++ {
        m := make(map[int]int, mapSize)
        for j := 0; j < mapSize; j++ {
            m[i] = i
        }
    }
}

以下是benchmark基准测试结果:

$ go test -benchmem -bench=. map_test.go
goos: windows
goarch: amd64
cpu: 12th Gen Intel(R) Core(TM) i7-12700
BenchmarkMakMapWithoutCap-20               32870             37345 ns/op               0 B/op          0 allocs/op
BenchmarkMakMapWithCap-20                  17340             74030 ns/op          319513 B/op          2 allocs/op
PASS
ok      command-line-arguments  3.753s

可以看出,使用cap参数的map实例的平均写性能是不使用cap参数的2倍。

文中代码均已上传至github