- 预分配容量
- 原理:在 Go 语言中,切片(Slice)是一种动态数组。当向切片中添加元素时,如果切片的容量不足,Go 会自动重新分配内存,这会涉及到数据的复制和内存的重新分配过程,从而影响性能。预分配容量可以避免这种频繁的内存重新分配。
- 示例:
- 假设我们要创建一个切片来存储 1000 个整数。不进行预分配容量的情况如下:
func withoutPreAlloc() []int { var s []int for i := 0; i < 1000; i++ { s = append(s, i) } return s }
- 进行预分配容量的情况:
func withPreAlloc() []int { s := make([]int, 0, 1000) for i := 0; i < 1000; i++ { s = append(s, i) } return s }
- 在性能测试中,可以使用
testing
包来比较这两种方式的性能差异。例如:
func TestSlicePreAlloc(t *testing.T) { t.Run("WithoutPreAlloc", func(t *testing.T) { withoutPreAlloc() }) t.Run("WithPreAlloc", func(t *testing.T) { withPreAlloc() }) }
- 通过运行
go test -bench=.
命令,可以看到预分配容量的方式在处理大量元素添加时通常会更快。
- 减少中间切片的创建
- 原理:每次创建新的切片都会涉及到一定的内存分配和数据复制操作。如果在一个循环或者频繁调用的函数中不断创建中间切片,会导致性能下降。
- 示例:
- 不好的做法:
func badPractice() []int { var result []int for i := 0; i < 10; i++ { temp := make([]int, i) for j := 0; j < i; j++ { temp[j] = j } result = append(result, temp...) } return result }
- 改进后的做法:
func goodPractice() []int { resultLen := 0 for i := 0; i < 10; i++ { resultLen += i } result := make([]int, 0, resultLen) offset := 0 for i := 0; i < 10; i++ { temp := make([]int, i) for j := 0; j < i; j++ { temp[j] = j } result = append(result, temp...) offset += i } return result }
- 在这个例子中,原做法在每次循环中都创建一个新的临时切片
temp
,并将其元素追加到result
切片中。改进后的做法先计算出最终result
切片的总长度,进行预分配容量,然后再将临时切片的元素逐个添加到result
切片中,减少了中间切片的创建次数。
- 利用切片的复用特性
- 原理:切片的底层是一个数组,当切片的容量大于长度时,可以对切片进行裁剪,重新利用剩余的容量来存储新的数据,从而避免不必要的内存分配。
- 示例:
- 比如有一个切片用来缓存数据,当缓存满了之后,我们可以清空切片的部分内容,重新利用它。
func reuseSlice() { cache := make([]int, 0, 10) for i := 0; i < 10; i++ { cache = append(cache, i) } // 清空切片的前5个元素,重新利用切片 cache = cache[5:] for i := 0; i < 5; i++ { cache = append(cache, i + 10) } }
- 在这个例子中,先创建了一个容量为 10 的切片并填充数据。然后通过
cache = cache[5:]
操作,将切片的前 5 个元素丢弃,利用剩下的容量来存储新的数据,这样就避免了重新分配内存来创建新的切片。
- 合理使用切片的复制操作(
copy
函数)
- 原理:当需要将一个切片的内容复制到另一个切片时,使用
copy
函数比手动逐个元素复制更高效。copy
函数会尽可能地复制元素,直到源切片或目标切片的长度用尽。 - 示例:
- 假设我们有两个切片,需要将一个切片的内容复制到另一个切片中。
func copySlice() { source := []int{1, 2, 3, 4, 5} target := make([]int, len(source)) copy(target, source) }
- 在这里,使用
copy
函数可以高效地将source
切片的内容复制到target
切片中。如果不使用copy
函数,手动逐个元素复制会更繁琐且可能效率更低。