Go 性能实战:用 sync.Pool 复用对象把 GC 压力降下来
test12026-09-251 次阅读
问题从哪来
高并发服务里,如果每处理一个请求都 new 一个临时大对象(比如字节缓冲、序列化中间结构),这些对象很快变成垃圾,GC 频率随之升高,STW 和 CPU 占用都上去了。sync.Pool 提供了一个进程级的临时对象池,让对象用完后归还、下次复用,从而削减分配次数。
最小可用示例
package main
import (
"bytes"
"sync"
)
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func handle(data []byte) []byte {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset() // 关键:复用前清空,别带着上次的数据
buf.Write(data)
out := append([]byte(nil), buf.Bytes()...)
bufPool.Put(buf) // 用完归还
return out
}
配合 bytes.Buffer 做 JSON 批量序列化
下面这个例子把多个结构体编码进一个缓冲,避免反复分配:
func encodeBatch(items []Item) ([]byte, error) {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer func() { bufPool.Put(buf) }()
enc := json.NewEncoder(buf)
for _, it := range items {
if err := enc.Encode(it); err != nil {
return nil, err
}
}
return append([]byte(nil), buf.Bytes()...), nil
}
使用准则
- 池化的对象必须 Reset 后才能再用,否则会混入上一次请求的脏数据,这是最常见的 bug。
- Put 回去的对象可能被任意时刻被 GC 清掉,所以它适合"临时、可重建"的对象,不能当作缓存或状态容器。
- New 函数要轻量,只在池空时才会调用;池满时 Put 的对象会被直接丢弃。
- 对象大小差异很大时,可以建多个按尺寸分级的池,避免小对象占用大缓冲。
如何验证收益
用 go 自带的基准测试对比有无 Pool 的分配次数:
func BenchmarkWithPool(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = handle(make([]byte, 1024))
}
}
跑 go test -bench=. -benchmem,重点看 allocs/op 这一列:引入 Pool 后,单次的堆分配数通常能降到接近 0。再结合线上 pprof 的 alloc_space 指标,就能确认 GC 压力是否真的下来了。
T
test1
文章作者
问题从哪来 高并发服务里,如果每处理一个请求都 new 一个临时大对象(比如字节缓冲、序列化中间结构),这些对象很快变...
- 分类
- 技术
- 发布时间
- 2026-09-25
- 字数
- 约 1298 字
- 阅读
- 1 次
