前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang内存GC平滑优化

Golang内存GC平滑优化

原创
作者头像
Luoyger
发布2024-03-13 11:12:06
2860
发布2024-03-13 11:12:06
举报

在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动态调整

研究GOGC和ballast机制原理(当时Golang1.19 GC新特性未发布),创新地结合两者特点实现了可自动调整GC阈值来降低GC消耗。其实现原理如下:

  1. 首先手动分配一个大的内存块(即ballast,可初始化时指定大小),提高触发GC的内存阈值,以稳定堆的大小,从而减少GC的频率和延迟。
  2. Golang默认的GOGC的值为100%,即如果应用程序有100M的活动堆,那么将在堆达到200M时触发GC。所以可以根据启动时设置的内存上限比例和当前已使用的内存比例动态调整GOGC,避免频繁触发GC,两种设置GOGC的情况如下:
  • 未超过内存上限时:GOGC= GOGCLimitPercent / curMemPercent – 1
  • 超过内存上限时:GOGC= preGOGC * (GOGCLimitPercent / curMemPercent)

其中GOGCLimitPercent 为初始配置的内存上限比例,preGOGC为上一次GOGC的值,curMemPercent为当前实际使用的内存使用比例。若GOGC小于0,则表示超过内存上限了,会根据上次GOGC值降低GOGC值,可以通过GC更快降低内存。

实验中对于优化前后两种情况模拟不断分配对象,查看GC日志。

优化前的日志如下,GC非常频繁。

优化后的日志如下,可以看到每次GC后还保持住了指定ballast大小(1G)的堆内存,GC频率明显降低。

图表数值如下:

代码语言:javascript
复制
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

计算代码如下:

代码语言:javascript
复制
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

在考虑到上述因素后,我们可以尝试引入一个平滑因子和内存使用趋势来优化这个公式。以下是一个可能的优化公式:

  1. 引入平滑因子alpha(0 < alpha < 1):
代码语言:javascript
复制
GOGC_new = alpha * GOGC_calculated + (1 - alpha) * GOGC_previous

其中,GOGC_calculated 是通过原始公式计算出的新的 GOGC 值,GOGC_previous 是上一次的 GOGC 值。

  1. 考虑内存使用趋势,我们可以通过计算内存使用量的移动平均来估计内存使用的趋势。例如,我们可以使用以下公式来计算移动平均:
代码语言:javascript
复制
moving_average = beta * current_memory_usage + (1 - beta) * previous_moving_average

其中,beta 是另一个平滑因子(0 < beta < 1),current_memory_usage 是当前的内存使用量,previous_moving_average 是上一次计算的移动平均。

然后,我们可以将移动平均替换原始公式中的 curMemPercent,得到新的 GOGC_calculated:

代码语言:javascript
复制
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。

  1. 在内存使用稳定的情况下:

假设当前内存使用比例 curMemPercent 为 50%,那么我们首先计算移动平均:

代码语言:javascript
复制
moving_average = beta * curMemPercent + (1 - beta) * previous_moving_average
               = 0.5 * 50% + 0.5 * 50% = 50%

然后,我们计算新的 GOGC_calculated:

代码语言:javascript
复制
GOGC_calculated = GOGCLimitPercent / moving_average - 1
                = 80% / 50% - 1 = 60%

最后,我们计算新的 GOGC:

代码语言:javascript
复制
GOGC_new = alpha * GOGC_calculated + (1 - alpha) * GOGC_previous
         = 0.5 * 60 + 0.5 * 100 = 80

所以,在内存使用稳定的情况下,新的 GOGC 为 80。

  1. 在内存使用快速增长的情况下:

假设当前内存使用比例 curMemPercent 为 70%,那么我们首先计算移动平均:

代码语言:javascript
复制
moving_average = beta * curMemPercent + (1 - beta) * previous_moving_average
               = 0.5 * 70% + 0.5 * 50% = 60%

然后,我们计算新的 GOGC_calculated:

代码语言:javascript
复制
GOGC_calculated = GOGCLimitPercent / moving_average - 1
                = 80% / 60% - 1 = 33.33%

最后,我们计算新的 GOGC:

代码语言:javascript
复制
GOGC_new = alpha * GOGC_calculated + (1 - alpha) * GOGC_previous
         = 0.5 * 33.33 + 0.5 * 80 = 56.67

所以,在内存使用快速增长的情况下,新的 GOGC 为 56.67,这将更早地触发 GC,以防止内存使用超过上限。

  1. 在流量突发的情况下:

假设当前内存使用比例 curMemPercent 突然增加到 90%,那么我们首先计算移动平均:

代码语言:javascript
复制
moving_average = beta * curMemPercent + (1 - beta) * previous_moving_average
               = 0.5 * 90% + 0.5 * 60% = 75%

然后,我们计算新的 GOGC_calculated:

代码语言:javascript
复制
GOGC_calculated = preGOGC * (GOGCLimitPercent / moving_average)
                = 56.67 * (80% / 75%) = 60.36

最后,我们计算新的 GOGC:

代码语言:javascript
复制
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

模拟内存变化相对稳定(同上)

模拟内存变化频繁(同上)

计算代码如下:

代码语言:javascript
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • GOGC动态调整
  • 移动平均平滑GOGC
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档

http://www.vxiaotou.com