关于 Go GC 优化的手段你知道的有哪些?比较常见的是通过调整 GC 的步调,以调整 GC 的触发频率。

  • 设置 GOGC
  • 设置 debug.SetGCPercent()

这两种方式的原理和效果都是一样的,GOGC 默认值是 100,也就是下次 GC 触发的 heap 的大小是这次 GC 之后的 heap 的一倍。

我们都知道 GO 的 GC 是标记-清除方式,当 GC 会触发时全量遍历变量进行标记,当标记结束后执行清除,把标记为白色的对象执行垃圾回收。值得注意的是,这里的回收仅仅是标记内存可以返回给操作系统,并不是立即回收,这就是你看到 Go 应用 RSS 一直居高不下的原因。在整个垃圾回收过程中会暂停整个 Go 程序(STW),Go 垃圾回收的耗时还是主要取决于标记花费的时间的长短,清除过程是非常快的。

设置 GOGC 的弊端

1. GOGC 设置比率的方式不精确

设置 GOGC 基本上我们比较常用的 Go GC 调优的方式,大部分情况下其实我们并不需要调整 GOGC 就可以,一方面是不涉及内存密集型的程序本身对内存敏感程度太低,另外就是 GOGC 这种设置比率的方式不精确,我们很难精确的控制我们想要的触发的垃圾回收的阈值。

2. GOGC 设置过小

GOGC 设置的非常小,会频繁触发 GC 导致太多无效的 CPU 浪费,反应到程序的表现就会特别明显。举个例子,对于 API 接口来说,导致的结果的就是接口周期性的耗时变化。这个时候你抓取 CPU profile 来看,大部分的耗时都集中在 GC 的相关处理上。

如上图,这是一次 prometheus 的查询操作,我们看到大部分的 CPU 都消耗在 GC 的操作上。这也是生产环境遇到的,由于 GOGC 设置的过小,导致过多的消耗都耗费在 GC 上。

3. 对某些程序本身占用内存就低,容易触发 GC

对 API 接口耗时比较敏感的业务,由于这种接口一般情况下内存占用都比较低,因为 API 接口变量的生命周期都比较短,这个时候 GOGC 置默认值的时候,也可能也会遇到接口的周期性的耗时波动。这是为什么呢?

因为这种接口本身占用内存比较低,每次 GC 之后本身占的内存比较低,如果按照上次 GC 后的 heap 的一倍的 GC 步调来设置 GOGC 的话,这个阈值其实是很容易就能够触发,于是就很容出现接口因为 GC 的触发导致额外的消耗。

4. GOGC 设置很大,有的时候又容易触发 OOM

那如何调整呢?是不是把 GOGC 设置的越大越好呢?这样确实能够降低 GC 的触发频率,但是这个值需要设置特别大才有效果,GOGC 一般需要设置 2000 左右。这样带来的问题,GOGC 设置的过大,如果这些接口突然接受到一大波流量,由于长时间无法触发 GC 可能导致 OOM。

由此,GOGC 对于这样的场景并不是很友好,那有没有能够精确控制内存,让其在 10G 的倍数时准确控制 GC 呢?

GO 内存 ballast

这就需要 Go ballast 出场了。什么是 Go ballast,其实很简单就是初始化一个生命周期贯穿整个 Go 应用生命周期的超大 slice。

func main() {
ballast := make([]byte, 10*1024*1024*1024) // 10G // do something runtime.KeepAlive(ballast)
}

上面的代码就初始化了一个 ballast,利用 runtime.KeepAlive 来保证 ballast 不会被 GC 给回收掉。

利用这个特性,就能保证 GC 在 10G 的一倍时才能被触发,这样就能够比较精准控制 GO GC 的触发时机。

这里你可能有一个疑问,这里初始化一个 10G 的数组,不就占用了 10 G 的物理内存呢? 答案其实是不会的。

package main

import (
"runtime"
"math"
"time"
) func main() {
ballast := make([]byte, 10*1024*1024*1024) <-time.After(time.Duration(math.MaxInt64))
runtime.KeepAlive(ballast)
}
$ ps -eo pmem,comm,pid,maj_flt,min_flt,rss,vsz --sort -rss | numfmt --header --to=iec --field 5 | numfmt --header --from-unit=1024 --to=iec --field 6 | column -t | egrep "[t]est|[P]I"

%MEM  COMMAND   PID    MAJFL      MINFL  RSS    VSZ
0.1 test 12859 0 1.6K 344M 11530184

这个结果是在 CentOS Linux release 7.9 验证的,我们看到占用的 RSS 真实的物理内存只有 344M,但是 VSZ 虚拟内存确实有 10G 的占用。

延伸一点,当怀疑我们的接口的耗时是由于 GC 的频繁触发引起的,我们需要怎么确定呢?首先你会想到周期性的抓取 pprof 的来分析,这种方案其实也可以,但是太麻烦了。其实可以根据 GC 的触发时间绘制这个曲线图,GC 的触发时间可以利用 runtime.Memstats 的 LastGC 来获取。

生产环境验证

  • 绿线 调整前 GOGC = 30
  • 黄线 调整后 GOGC 默认值,ballast = 100G

这张图相同的流量压力下,ballast 的表现明显偏好

结论

本篇文章只是简单的阐述了 Go ballast 的使用,不过 Go ballast 是官方比较认可的方案,具体可以参见 issue 23044。很多开源程序,如 tidbcortex 都实现了 go ballast,如果你的程序饱受 GOGC 的问题影响或者周期性的耗时不稳定,不妨尝试下 go ballast。

当然强烈推荐你看下twitch.tv 这篇文章,相信让你会对 GOGC 以及 ballast 的运用理解的更加透彻。

性能优化 | Go Ballast 让内存控制更加丝滑的更多相关文章

  1. JVM性能优化系列-(1) Java内存区域

    1. Java内存区域 1.1 运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.主要包括:程序计数器.虚拟机栈.本地方法栈.Java堆.方法区(运 ...

  2. android app性能优化大汇总(内存性能优化)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  3. Android性能优化之常见的内存泄漏

    前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary.MAT等工具来检 ...

  4. Spark性能优化(1)——序列化、内存、并行度、数据存储格式、Shuffle

    序列化 背景: 在以下过程中,需要对数据进行序列化: shuffling data时需要通过网络传输数据 RDD序列化到磁盘时 性能优化点: Spark默认的序列化类型是Java序列化.Java序列化 ...

  5. 【好书摘要】性能优化中CPU、内存、磁盘IO、网络性能的依赖

    系统优化是一项复杂.繁琐.长期的工作,优化前需要监测.采集.测试.评估,优化后也需要测试.采集.评估.监测,而且是一个长期和持续的过程,不 是说现在优化了,测试了,以后就可以一劳永逸了,也不是说书本上 ...

  6. Android性能优化:谈话Bitmap内存管理和优化

    最近除了那些忙着项目开发的事情,目前正在准备我的论文.短的时间没有写博客,今晚难得想总结.只要有一点时间.因此,为了凑合用,行.唠叨罗嗦,直接进入正题. 从事Android自移动终端的发展,想必是常常 ...

  7. 性能优化中CPU、内存、磁盘IO、网络性能的依赖(转)

    关于系统性能优化,推荐一篇不错的博客! 系统优化是一项复杂.繁琐.长期的工作,优化前需要监测.采集.测试.评估,优化后也需要测试.采集.评估.监测,而且是一个长期和持续的过程,不 是说现在优化了,测试 ...

  8. 性能优化中CPU、内存、磁盘IO、网络性能的依赖

    系统优化是一项复杂.繁琐.长期的工作,优化前需要监测.采集.测试.评估,优化后也需要测试.采集.评估.监测,而且是一个长期和持续的过程,不 是说现在优化了,测试了,以后就可以一劳永逸了,也不是说书本上 ...

  9. 转-性能优化中CPU、内存、磁盘IO、网络性能的依赖

    转自:https://www.cnblogs.com/Javame/p/3665565.html 系统优化是一项复杂.繁琐.长期的工作,优化前需要监测.采集.测试.评估,优化后也需要测试.采集.评估. ...

随机推荐

  1. .NET 排序 Array.Sort<T> 实现分析

    System.Array.Sort<T> 是.NET内置的排序方法, 灵活且高效, 大家都学过一些排序算法,比如冒泡排序,插入排序,堆排序等,不过你知道这个方法背后使用了什么排序算法吗? ...

  2. C++面向行输入:get()与getline()

    面向行的输入:get()与getline() 引入: char a = 's';//这样的语句合法 char b = "s";//不合法 /* "S"不是字符常 ...

  3. macbook air m1上传文件到github

    一,首先安装git,打开ssh文件里的id_rsa.pub,然后复制所有内容. 二,github上申请自己的账号,右上角settings里选择SSH and GPG keys,点击new ssh ke ...

  4. linux 信号与处理

    一.linux信号是什么 基本概念 信号是事件发生时对进程的通知机制,也就是所谓的软件中断.信号和硬件的中断类似,是软件层对中断机制的模拟,在多数情况下是无法预测信号产生的时间,所以软件层提供了一种处 ...

  5. Vue3学习(三)之网站首页布局开发

    一.前言 上篇文章已经提到集成Ant Design Vue后,和Element Ui一样,还是组件的使用,然后就是复制粘贴改了. 二.实际案例 先搞个布局布局看看,也就是我们说的layout,如下图: ...

  6. C++ IDE或编辑器安装

    IDE介绍 上节课我们讲了C++编译器,可是没有好的编辑器,只用记事本打代码,这谁受得了.Linux vim至少还有代码高亮(即我作文里经常会出现的"彩色的代码"),记事本连高亮都 ...

  7. vue基础-动态样式&表单绑定&vue响应式原理

    动态样式 作用:使用声明式变量来控制class和style的值 语法: :class/:style 注意:尽可能不要把动态class和静态class一起使用,原因动态class起作用的时间会比较晚,需 ...

  8. python解释器和Pycharm编辑器安装使用完整详细教程

    一.官网下载或软件管家公众号下载 二.安装Python解释器 1.选择自定义安装并添加到环境变量 2.检验Python是否安装成功 三.安装pycharm编辑器 1.点击安装,修改安装路径,建议安装C ...

  9. 《JavaScript DOM编程艺术》:innerHTML

    来源:第七章 动态创建标记 innerHTML: 1.HTML页面建立空白div: <div id="testdiv"> </div> <script ...

  10. 【UE4 C++】抛物线路径、发射轨道相关

    基于UGameplayStatics Blueprint_PredictProjectilePath_ByObjectType 根据 Object Type,算出抛物线的点集合和检测结果 static ...