Go GC 调优踩坑:为啥低延迟场景要调小 GOGC?

做实时通信或者金融这类低延迟业务,一般都习惯把 GOGC 调小到 50 甚至更低,我一开始很好奇,调小了 GC 触发更频繁,不是应该更卡,程序更慢吗,咋反而能实现低延迟?翻了一堆源码做了几次测试后想通了,不是次数多就差,而是要看单次 GC 的影响。

GOGC 默认是100,触发 GC 的依据是:

下一次 GC 的触发阈值 = 上一次 GC 完成后堆内存的存活大小 × (1 + GOGC/100)

比如如果上一次 GC 结束后,内存里还剩 1G 的存活对象,GOGC=100 时,内存涨到 2G 才会触发下一次 GC;要是把 GOGC 调到 50,内存涨到 1.5G 就会触发 GC。一句话总结:GOGC 值越小,GC 触发越早、越频繁;值越大,触发越晚、越稀疏。我之前的误区就是觉得次数多了肯定耗资源,却忽略了单次 GC 要干的活有多少,这其实才是影响程序延迟的关键。

调小 GOGC 的核心逻辑是:

调小后 GC 触发更早更频繁,但每次 GC 要干的活极少

比如我要打扫房间:

GOGC=50:房间刚乱 50% 就打扫,一次只扫 10 分钟,虽然打扫次数变多,但每次卡顿时间极短,业务几乎无感知。

GOGC=100:房间堆到 2 倍乱才打扫,一次要扫 1 小时,中途没法干别的(卡顿 1 小时)。

肯定有人会问:就算单次轻量,次数多了总开销不是也会涨吗?难道不会拖慢程序?

实话说,会涨,但涨的幅度可以忽略不计,而且完全在低延迟场景的承受范围内。一方面,低延迟业务的服务器,CPU 一般都有冗余,我的核心诉求是响应快不卡顿,不是把 CPU 跑满追求吞吐,这点 GC 的额外开销,服务器完全扛得住。

真正的程序变慢,从来不是来自频繁的轻量 GC,而是来自一次重量级 GC 的资源抢占,GC 占着 CPU 不放,业务代码跑不动,响应自然就慢了。调小 GOGC,本质就是避免 GC 变成 重量级。

同理对于 CPU 密集型任务则是建议调大 GOGC,比如大量数据处理、后台计算之类的,这类场景不怕轻微卡顿,怕的是 GC 频繁抢 CPU, 降低 GC 频率可以把更多 CPU 资源留给业务逻辑。