基于切片扩容机制的性能优化策略
- 预先分配足够容量
- 原理:Go 切片在容量不足时会进行扩容,扩容时会重新分配内存,将原切片数据复制到新的内存地址。预先分配足够容量可以避免频繁的内存重新分配和数据复制。
- 示例:
// 预先分配容量为100的切片
var s = make([]int, 0, 100)
for i := 0; i < 100; i++ {
s = append(s, i)
}
- 尽量减少扩容次数
- 原理:每次扩容都涉及内存分配和数据复制,开销较大。在已知数据规模的情况下,合理预估容量,减少扩容发生的次数。
- 示例:假设要从数据库读取大量数据到切片,先查询数据总数,然后按总数预分配容量。
// 假设从数据库获取数据总数
totalCount := getTotalCountFromDB()
var dataSlice = make([]DataStruct, 0, totalCount)
rows, err := db.Query("SELECT * FROM table")
if err != nil {
// 处理错误
}
defer rows.Close()
for rows.Next() {
var d DataStruct
err := rows.Scan(&d.Field1, &d.Field2)
if err != nil {
// 处理错误
}
dataSlice = append(dataSlice, d)
}
- 避免不必要的切片复制
- 原理:当对切片进行操作导致其容量不足而扩容时,会发生数据复制。如果能避免这种因容量不足引发的复制,可提高性能。
- 示例:尽量在同一个切片上进行追加操作,而不是频繁创建新切片。
// 不好的做法
var s1 []int
for i := 0; i < 10; i++ {
var s2 []int
s2 = append(s1, i)
s1 = s2
}
// 好的做法
var s []int
for i := 0; i < 10; i++ {
s = append(s, i)
}
- 合理使用切片截取
- 原理:切片截取操作会创建新的切片结构,共享底层数组。但如果截取后原切片不再使用,应及时释放底层数组占用的内存。
- 示例:
s := make([]int, 100)
// 填充数据
for i := 0; i < 100; i++ {
s[i] = i
}
// 截取前10个元素
s1 := s[:10]
// 原切片s不再使用,可通过重新分配内存释放其底层数组
s = nil
- 注意扩容因子
- 原理:Go 切片扩容时,新容量一般是原容量的 2 倍(当原容量小于 1024 时),大于 1024 时,新容量会增加原容量的 1/4。了解这个机制有助于更精确地预估内存使用。
- 应用:对于大规模数据处理,如果一开始分配过小的容量,随着数据增加,扩容时可能会导致内存占用快速增长,所以初始容量分配要结合数据规模和扩容因子综合考虑。