跳到主要内容

内存性能优化

问题

Go 中如何减少内存分配,降低 GC 压力?

答案

核心原则

减少堆分配 = 减少 GC 压力 = 降低延迟

常见优化手段

手段优化前优化后
预分配 slicevar s []ints := make([]int, 0, n)
strings.Builders += "a"b.WriteString("a")
sync.Pool每次 new对象复用
避免 []bytestring 转换频繁转换使用 unsafe 零拷贝
值类型嵌入指针字段值字段
数组代替 slice[]T[N]T(编译时已知大小)

实战

预分配 slice

// ❌ 多次扩容,产生多次分配
func collect(n int) []int {
var result []int
for i := 0; i < n; i++ {
result = append(result, i)
}
return result
}

// ✅ 预分配容量
func collect(n int) []int {
result := make([]int, 0, n)
for i := 0; i < n; i++ {
result = append(result, i)
}
return result
}

sync.Pool 复用对象

var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}

func process(data []byte) string {
buf := bufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufPool.Put(buf)
}()

buf.Write(data)
return buf.String()
}

避免 string/[]byte 转换

import "unsafe"

// 零拷贝 string → []byte(只读场景)
func stringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}

// 零拷贝 []byte → string
func bytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}

检查工具

# 逃逸分析:查看哪些变量逃逸到堆上
go build -gcflags="-m" ./...

# Benchmark 内存分配
go test -bench=. -benchmem

# pprof heap profile
go tool pprof http://localhost:6060/debug/pprof/heap

常见面试问题

Q1: 什么情况会导致变量逃逸到堆?

答案

  1. 返回指针:return &obj
  2. 发送到 channel:ch <- &obj
  3. 存入 interface:var i interface{} = obj
  4. 闭包引用:闭包中使用了外部变量
  5. 超大对象:栈放不下

详见 逃逸分析

Q2: sync.Pool 的对象什么时候被回收?

答案每次 GC 时。sync.Pool 有 victim cache(Go 1.13+),对象在两次 GC 后才被回收,但不能依赖对象一直存在。Pool 适合做临时缓存,不适合做持久存储。

相关链接