GC 优化方式
GC 优化方式
一、GC 问题来源
- 大量指针对象创建,注意:值类型的 struct ,若内部包含指针成员,也会被 GC 追踪
- 逃逸到堆上的对象
- 大对象分配,超过 32KB 的对象会被认为是大对象,直接分配到堆上
GC 负担 ≈ 堆上对象数 + 指针对象数
二、定位问题点
- 从架构层面分析,梳理数据流程和并发模型
- go pprof 分析工具
- cursor 等 AI 工具分析代码,会提供一些优化思路
三、常见优化方式
sync.Pool 池化对象
适用场景:高频创建 + 短生命周期(不跨 GC 周期) + 大对象
NOTE:若缓存对象中包含指针成员(指针、字符串等),归还时务必去掉引用,避免拖住 GC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (p *HostPartitioner) putChunks(chunks []model.TraceMetaChunk) {
if cap(chunks) > p.config.CacheChunksSize {
return
}
for i := range chunks {
chunks[i].Callback = nil
chunks[i].TraceId = ""
...
}
chunks = chunks[:0]
p.chunksPool.Put(chunks)
}
[]byte缓冲区- JSON encode/decode 临时结构
- proto marshal/Unmarshal 临时结构
- 临时对象
不是适用场景:
- 长生命周期对象;sync.Pool 只会缓存最近使用的对象,长期不使用的对象会被 GC 回收
- 小对象(不逃逸);小对象会在栈上分配,使用 sync.Pool 反而跟慢
slice/map 提前分配cap和内存复用
典型做法:提前分配空间 batch := make([]object, 0, n), append 时不会触发扩容, 复用内存 batch = batch[:0]
object 尽可能是值类型,若是指针类型,仍会增加 GC 负担
worker 代替闭包
闭包通常导致对象逃逸,增加 GC 压力。
使用 worker 传值的方式代替。
控制 goroutine 数量
每个 goroutine 都有栈,可能持有堆对象。
过多 goroutine → GC 扫描压力大。
调整 GOGC 参数
减低 GC 频率:GOGC=200, GOMEMLIMIT=60GiB
This post is licensed under CC BY 4.0 by the author.