切片性能及陷阱
最后更新于:2022-04-02 02:36:14
[TOC]
> [参考](https://github.com/geektutu/high-performance-go/blob/master/charpter-2/hpg-slice.md)
## 数组
- 数组是值传递,与C的数组不同,C中指针传递,指向第一个元素
- Go 中为了避免复制数组,一般会传递指向数组的指针
```
func square(arr *[3]int) {
for i, num := range *arr {
(*arr)[i] = num * num
}
}
func TestArrayPointer(t *testing.T) {
a := [...]int{1, 2, 3}
square(&a)
fmt.Println(a) // [1 4 9]
if a[1] != 4 && a[2] != 9 {
t.Fatal("failed")
}
}
```
## 切片
- 切片操作并不复制切片指向的元素,创建一个新的切片会复用原来切片的底层数组,因此切片操作是非常高效的
- 切片本质是一个数组片段的描述,包括了数组的指针,这个片段的长度和容量(不改变内存分配情况下的最大长度)
```
struct {
ptr *[]T
len int
cap int
}
```
![](https://geektutu.com/post/hpg-slice/slice.jpg)
```
nums := make([]int, 0, 8)
nums = append(nums, 1, 2, 3, 4, 5)
nums2 := nums[2:4]
printLenCap(nums) // len: 5, cap: 8 [1 2 3 4 5]
printLenCap(nums2) // len: 2, cap: 6 [3 4]
nums2 = append(nums2, 50, 60)
printLenCap(nums) // len: 5, cap: 8 [1 2 3 4 50]
printLenCap(nums2) // len: 4, cap: 6 [3 4 50 60]
```
## 性能陷阱
### 大量内存得不到释放
```
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
}
```
```
slice_test1.go 100.14 MB
slice_test2.go 3.14 MB
```
1. 申请的 100 个 1 MB 大小的内存没有被回收。因为切片虽然只使用了最后 2 个元素,但是因为与原来 1M 的切片引用了相同的底层数组,底层数组得不到释放,因此,最终 100 MB 的内存始终得不到释放
2. 而`lastNumsByCopy`仅消耗了 3.14 MB 的内存。这是因为,通过`copy`,指向了一个新的底层数组,当 origin 不再被引用后,内存会被垃圾回收(garbage collector, GC)
';