切片和map
是Go
语言中重要数据类型,也是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。