Go减少内存分配实践
内存分配触发GC,减少分配是性能优化的关键。
预分配容量
Slice预分配
Go
// 不推荐:动态增长,多次分配
var result []int
for i := 0; i < 1000; i++ {
result = append(result, i) // 多次扩容
}
// 推荐:预分配容量
result := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
result = append(result, i) // 无扩容
}
// 或直接设置长度
result := make([]int, 1000)
for i := 0; i < 1000; i++ {
result[i] = i
}
Map预分配
Go
// 不推荐:动态增长
m := make(map[string]int)
for i := 0; i < 1000; i++ {
m[string(i)] = i // 多次扩容
}
// 推荐:预分配容量(Go 1.11+)
m := make(map[string]int, 1000)
sync.Pool复用对象
Go
var bufPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func processData() {
// 从池获取
buf := bufPool.Get().([]byte)
defer bufPool.Put(buf) // 归还
// 使用buf处理数据
// ...
}
Pool使用场景
- 临时缓冲区
- 短生命周期对象
- 高频创建销毁的对象
Pool对象可能被GC回收,不能存储长期数据。
字符串优化
避免字符串拼接
Go
// 不推荐:多次分配
var result string
for _, s := range parts {
result += s // 每次创建新字符串
}
// 推荐:strings.Join一次分配
result := strings.Join(parts, "")
// 或使用strings.Builder
var b strings.Builder
for _, s := range parts {
b.WriteString(s)
}
result := b.String()
预分配Builder容量
Go
var b strings.Builder
b.Grow(1000) // 预分配容量
for i := 0; i < 100; i++ {
b.WriteString("data")
}
避免频繁创建临时对象
使用局部变量
Go
// 不推荐:每次创建新对象
func process() {
buf := make([]byte, 1024) // 每次调用都分配
// ...
}
// 推荐:使用局部变量或Pool
var bufPool = sync.Pool{New: func() interface{} {
return make([]byte, 1024)
}}
func process() {
buf := bufPool.Get().([]byte)
defer bufPool.Put(buf)
// ...
}
避免闭包逃逸
Go
// 不推荐:每次创建闭包
func handler(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 100; i++ {
go func(n int) {
process(n) // 创建闭包对象
}(i)
}
}
// 推荐:直接调用
func handler(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 100; i++ {
go process(i) // 无闭包创建
}
}
slice复用技巧
清空slice复用底层数组
Go
var data []int
// 使用后清空,复用底层数组
func clearAndReuse() {
data = data[:0] // 清空但保留容量
// 继续使用,无需新分配
}
截取slice避免新分配
Go
original := make([]byte, 1024)
// 截取使用,共享底层数组
part := original[:100] // 无新分配
// 注意:修改part会影响original
优化策略对比
| 技术 | 适用场景 | 效果 |
|---|---|---|
| make预分配 | 已知大小 | 避免扩容 |
| sync.Pool | 临时对象 | 复用减少GC |
| strings.Join | 字符串拼接 | 单次分配 |
| slice[:0]清空 | 循环使用 | 复用底层数组 |
| 预分配Builder | 大字符串构建 | 避免扩容 |
分配监控
Go
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("堆分配: %d MB\n", m.HeapAlloc/1024/1024)
fmt.Printf("分配次数: %d\n", m.Mallocs)
使用pprof分析:
Bash
go tool pprof http://localhost:6060/debug/pprof/allocs
要点总结
- 预分配slice/map容量,避免动态扩容
- sync.Pool复用临时对象
- strings.Join/Builder替代循环拼接
- slice[:0]清空复用底层数组
- 避免闭包和不必要临时对象
- 用pprof监控分配热点
📝 发现内容有误?点击此处直接编辑