Post

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.