在Golang中,GOGC的值决定了在两次连续的垃圾收集之间,堆内存可以增长的百分比。具体来说,如果GOGC的值为X,那么当堆内存增长到上一次垃圾收集后的堆内存的(100+X)%时,就会触发新的垃圾收集。
GOGC根据当前活动堆大小使用配置的GOGC值(允许的当前Live Heap峰值内存百分比)来决定是否启动垃圾收集,比如当前活动堆大小20MB,GOGC 设置为 100 ,当新分配内存堆峰值内存大于20MB时将触发垃圾收集。
Go MemoryLimit(在Go 1.19版本可用)
设置Go运行时可使用的堆内存,该限制并不是强制性,如设置Limit=20MB,程序实际需要100MB,实际也能够分配到100MB,但是垃圾收集器会频繁进行GC来试图保持不可能到达的Limit值。
研究GOGC和ballast机制原理(当时Golang1.19 GC新特性未发布),创新地结合两者特点实现了可自动调整GC阈值来降低GC消耗。其实现原理如下:
其中GOGCLimitPercent 为初始配置的内存上限比例,preGOGC为上一次GOGC的值,curMemPercent为当前实际使用的内存使用比例。若GOGC小于0,则表示超过内存上限了,会根据上次GOGC值降低GOGC值,可以通过GC更快降低内存。
实验中对于优化前后两种情况模拟不断分配对象,查看GC日志。
优化前的日志如下,GC非常频繁。
优化后的日志如下,可以看到每次GC后还保持住了指定ballast大小(1G)的堆内存,GC频率明显降低。
图表数值如下:
curMemPercent:0.1, GOGC:7.0
curMemPercent:0.2, GOGC:3.0
curMemPercent:0.3, GOGC:1.666666666666667
curMemPercent:0.4, GOGC:1.0
curMemPercent:0.5, GOGC:0.6000000000000001
curMemPercent:0.6, GOGC:0.3333333333333335
curMemPercent:0.7, GOGC:0.14285714285714302
curMemPercent:0.8, GOGC:0.0
curMemPercent:0.9, GOGC:0.0
curMemPercent:1.0, GOGC:0.0
计算代码如下:
import matplotlib.pyplot as plt
# 假设你已经有了以下数据
GOGCLimitPercent = 0.8 # 内存上限比例
curMemPercent_list = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] # 当前实际使用的内存使用比例
preGOGC = 1 # 上一次GOGC的值
GOGC_list = [] # 用于存储每次计算的GOGC值
for curMemPercent in curMemPercent_list:
if curMemPercent <= GOGCLimitPercent:
GOGC = GOGCLimitPercent / curMemPercent - 1
else:
GOGC = preGOGC * (GOGCLimitPercent / curMemPercent)
print(f"curMemPercent:{curMemPercent}, GOGC:{GOGC}")
GOGC_list.append(GOGC)
preGOGC = GOGC
# 绘制图像
plt.plot(curMemPercent_list, GOGC_list)
plt.xlabel('Current Memory Usage Percentage')
plt.ylabel('GOGC')
plt.title('GOGC vs Current Memory Usage Percentage')
plt.grid(True)
plt.savefig('GOGC_vs_MemoryUsage.png') # 保存图像到本地
plt.show()
plt.savefig("test.png")
在考虑到上述因素后,我们可以尝试引入一个平滑因子和内存使用趋势来优化这个公式。以下是一个可能的优化公式:
GOGC_new = alpha * GOGC_calculated + (1 - alpha) * GOGC_previous
其中,GOGC_calculated 是通过原始公式计算出的新的 GOGC 值,GOGC_previous 是上一次的 GOGC 值。
moving_average = beta * current_memory_usage + (1 - beta) * previous_moving_average
其中,beta 是另一个平滑因子(0 < beta < 1),current_memory_usage 是当前的内存使用量,previous_moving_average 是上一次计算的移动平均。
然后,我们可以将移动平均替换原始公式中的 curMemPercent,得到新的 GOGC_calculated:
GOGC_calculated = GOGCLimitPercent / moving_average - 1 (未超过内存上限时)
GOGC_calculated = preGOGC * (GOGCLimitPercent / moving_average) (超过内存上限时)
这样,我们就得到了一个考虑了平滑因子和内存使用趋势的优化公式。这个公式可以更平滑地处理内存增长和缩减,但具体的效果会取决于你的应用程序的特性和需求。
当alpha越趋近于1,表示受之前的GOGC值影响越小,当beta越趋近于1,表示受之前的活动堆大小影响越小,这两者都等于1时,就退化为初始的简单计算方案。
计算举例
假设我们的内存上限比例 GOGCLimitPercent 为 80%,平滑因子 alpha 和 beta 分别为 0.5,初始的 GOGC 为 100。
假设当前内存使用比例 curMemPercent 为 50%,那么我们首先计算移动平均:
moving_average = beta * curMemPercent + (1 - beta) * previous_moving_average
= 0.5 * 50% + 0.5 * 50% = 50%
然后,我们计算新的 GOGC_calculated:
GOGC_calculated = GOGCLimitPercent / moving_average - 1
= 80% / 50% - 1 = 60%
最后,我们计算新的 GOGC:
GOGC_new = alpha * GOGC_calculated + (1 - alpha) * GOGC_previous
= 0.5 * 60 + 0.5 * 100 = 80
所以,在内存使用稳定的情况下,新的 GOGC 为 80。
假设当前内存使用比例 curMemPercent 为 70%,那么我们首先计算移动平均:
moving_average = beta * curMemPercent + (1 - beta) * previous_moving_average
= 0.5 * 70% + 0.5 * 50% = 60%
然后,我们计算新的 GOGC_calculated:
GOGC_calculated = GOGCLimitPercent / moving_average - 1
= 80% / 60% - 1 = 33.33%
最后,我们计算新的 GOGC:
GOGC_new = alpha * GOGC_calculated + (1 - alpha) * GOGC_previous
= 0.5 * 33.33 + 0.5 * 80 = 56.67
所以,在内存使用快速增长的情况下,新的 GOGC 为 56.67,这将更早地触发 GC,以防止内存使用超过上限。
假设当前内存使用比例 curMemPercent 突然增加到 90%,那么我们首先计算移动平均:
moving_average = beta * curMemPercent + (1 - beta) * previous_moving_average
= 0.5 * 90% + 0.5 * 60% = 75%
然后,我们计算新的 GOGC_calculated:
GOGC_calculated = preGOGC * (GOGCLimitPercent / moving_average)
= 56.67 * (80% / 75%) = 60.36
最后,我们计算新的 GOGC:
GOGC_new = alpha * GOGC_calculated + (1 - alpha) * GOGC_previous
= 0.5 * 60.36 + 0.5 * 56.67 = 58.52
所以,在流量突发的情况下,新的 GOGC 为 58.52,这将更频繁地触发 GC,以应对流量突增带来的内存压力。
根据计算公式进行画图,可以得到内存的使用率和GOGC的变化趋势,如下图,内存使用越高,GOGC值越低,GC越频繁,整个GOGC的变化比较平滑,内存变化也会随着平滑。
(1)当alpha=0.5, beta=0.5, previous_moving_average=0,GOGCLimitPercent=0.8,GOGC=1
模拟内存变化相对稳定(内存使用率在40%~60%内波动,横坐标是随时间变化的100个数据点,不是内存使用率)
模拟内存变化频繁(内存使用率在10%~90%内波动,横坐标是随时间变化的100个数据点,不是内存使用率)
(2)当alpha=0.9, beta=0.9,previous_moving_average=0,GOGCLimitPercent=0.8,GOGC=1
模拟内存变化相对稳定(同上)
模拟内存变化频繁(同上)
计算代码如下:
import matplotlib.pyplot as plt
# 参数设置
alpha = 0.5
beta = 0.5
GOGCLimitPercent = 0.8
GOGC_previous = 100
previous_moving_average = 0.5
# 模拟内存使用量的变化
curMemPercent_list = [i/100 for i in range(10, 101, 10)]
# 存储计算结果
GOGC_list = []
for curMemPercent in curMemPercent_list:
# 计算移动平均
moving_average = beta * curMemPercent + (1 - beta) * previous_moving_average
previous_moving_average = moving_average
# 计算新的 GOGC_calculated
if curMemPercent <= GOGCLimitPercent:
GOGC_calculated = GOGCLimitPercent / moving_average - 1
else:
GOGC_calculated = GOGC_previous * (GOGCLimitPercent / moving_average)
# 计算新的 GOGC
GOGC_new = alpha * GOGC_calculated + (1 - alpha) * GOGC_previous
GOGC_previous = GOGC_new
print(f"curMemPercent:{curMemPercent}, GOGC:{GOGC_new}")
GOGC_list.append(GOGC_new)
# 绘制曲线
plt.plot(curMemPercent_list, GOGC_list)
plt.xlabel('Memory Usage')
plt.ylabel('GOGC')
plt.title('GOGC vs Memory Usage')
plt.grid(True)
plt.show()
plt.savefig("test2.png")
总结:在内存变化频繁的情况下,可以将alpha和beta系数调低,GOGC的变化会更加平滑。
移动平均的概念:https://zhuanlan.zhihu.com/p/151786842
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。