go切片性能及陷阱
数组
go语言中,切片是数组之上的抽象数据类型,而数组类型定义了长度和元素类型,且数组的长度和元素类型不可变。
数组初始化: arr := [3]{1,2,3}
注意,数组数量属于值类型,当数组被当做参数变量进行赋值或传递时,实际上会复制整个数组。因此,为了避免数组赋值,一般传递数组的指针。
切片
切片比数组更具灵活性,不需要指定长度。
切片初始化:langs := []string{"Go", "Python", "C"} 或 make([]T, len, cap)
注意,切片可以扩容,且切片操作并不复制切片指向的元素,创建一个新的切片会复用原来切片的底层数组,因此切片操作是非常高效的。
切片的性能陷阱
func lastNumsBySlice(origin []int) []int {
return origin[len(origin)-2:]
}
func lastNumsByCopy(origin []int) []int {
result := make([]int, 2)
copy(result, origin[len(origin)-2:])
return result
}
func printMem(t *testing.T) {
t.Helper()
var rtm runtime.MemStats
runtime.ReadMemStats(&rtm)
t.Logf("%.2f MB", float64(rtm.Alloc)/1024./1024.)
}
func testLastChars(t *testing.T, f func([]int) []int) {
t.Helper()
ans := make([][]int, 0)
for k := 0; k < 100; k++ {
origin := generateWithCap(128 * 1024) // 1M
ans = append(ans, f(origin))
runtime.GC()
}
printMem(t)
fmt.Println(len(ans))
}
func TestLastCharsBySlice(t *testing.T) { testLastChars(t, lastNumsBySlice) }
func TestLastCharsByCopy(t *testing.T) { testLastChars(t, lastNumsByCopy) }
# go test -run=^TestLastChars -v
=== RUN TestLastCharsBySlice
slice_test.go:43: 100.27 MB
100
--- PASS: TestLastCharsBySlice (0.28s)
=== RUN TestLastCharsByCopy
slice_test.go:44: 0.28 MB
100
--- PASS: TestLastCharsByCopy (0.25s)
PASS
- 可以看到
lastNumsBySlice直接引用切片,底层数组在printMem()前并不会释放内存 - 而
lastNumsByCopy使用复制方式,底层数组会被释放内存