在优化go
程序时,内存优化是其中一项很重要的内容,减轻gc
的压力,能够极大的优化我们的程序运行效率。
今天先来看一下两个与gc
相关的环境变量:gctrace
和GOGC
gctrace
gctrace
本身是GODEBUG
这个环境变量中的一个选项,用来开启gc
日志的。每次当完成一次gc
扫描时,就会打印出本次gc
的相关信息,我们可以用来监控程序的内存情况
1 | $ GODEBUG=gctrace=1 GO_BIN # 开启gctrace |
每一行对应一次gc
,具体的输出格式如下:
1 | gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P [(forced)] |
gc #
:第几轮gc,从1开始递增@#s
:程序总的运行时间,单位s
#%
:从程序开始到现在运行gc
的时间占比#+#+# ms clock
:对应 第一次STW
,终止SWEEP
、开启写屏障 + 并发Mark
和Scan
+ 第二次STW
,结束Mark
这三个阶段wall-clock
的耗时,单位为ms
#+#/#/#+# ms cpu
:对应 第一次STW
+ 并发标记:Assist Time
/ 并发标记:Background GC time
/ 并发标记:Idle GC time
+ 第二次STW
结束Mark
这几个阶段的cpu
时间,单位ms
#->#-># MB
:分别对应gc
开始时的堆大小、gc
结束时的堆大小以及live heap
(gc mark
阶段标记为黑色的内存总量)的大小# MB goal
:目标heap size
# P
:使用的P
数量(forced)
:如果调用runtime.GC()
强制触发gc
除了输出gc
的信息,当runtime
向操作系统归还内存时,也会打印出信息,比如上面的:
1 | scvg1: inuse: 4, idle: 58, sys: 63, released: 0, consumed: 63 (MB) |
scvg#
:第几次归还,从1开始计数inuse: #
:正在使用或部分被使用的spans
的内存大小,单位MB
idle: #
:空闲等待归还给操作系统的spans
的内存大小,单位MB
;sys: #
:从操作系统映射的内存大小,实际上是gc
堆可访问的内存虚拟地址空间,单位MB
released: #
:本次归还给操作系统的内存大小,单位MB
consumed: #
:从操作系统分配的内存大小,等于sys - released
GOGC
根据runtime/mgc.go
中的注释:
Next GC is after we’ve allocated an extra amount of memory proportional to
the amount already in use. The proportion is controlled by GOGC environment variable
(100 by default). If GOGC=100 and we’re using 4M, we’ll GC again when we get to 8M
(this mark is tracked in next_gc variable). This keeps the GC cost in linear
proportion to the allocation cost. Adjusting GOGC just changes the linear constant
(and also the amount of extra memory used).
在go1.5
之前,运行gc mark
阶段会stop the world
, 能够根据next_gc
变量(也就是goal heap size,可以直接通过GOGC
变量调整)精确地控制堆内存的增长:
但是go1.5
之后,gc mark
可以跟用户协程并发运行,因此在gc
执行过程中仍然会有新的内存被分配,因此gc
的触发点需要相对next_gc
提前:
如上图所示,Hm(n-1)
表示上一次gc
结束后的堆大小,而Hg
是next_gc
,而我们在Ht
触发gc
,因为gc过程中可能会有新的内存分配,当gc
结束时,当前的堆大小为Ha
。go
的gc
实现,需要提供一种动态调整的机制,根据内存分配情况调整Ht
的值,使得Ha
能够与Hg
尽量接近。
总体来说,我们可以通过设置GOGC
的值来调整gc
的触发阈值:
- 当小于零或者等于
off
时,将会关闭gc
- 设置较大的值:减少
gc
触发,但是会增加内存占用 - 设置偏小的值:频繁触发
gc