Go 1.19中终于实现了SetMemoryLimit的功能

Go 1.19中终于实现了 SetMemoryLimit的功能。Go的GC并不像Java那样提供了很多的参数可以调整,目前也就有 GOGC这么一个参数,所以能增加一个可以调整GC的参数确实让人兴奋。

一直关注Go性能同学一定知道,最近几年有两个调整Go GC的hack方式:

  • ballast[1]: 压舱石技术。使用一个”虚假”的内存占用,让Go运行时难以达到触发GC的阈值,来实现减少GC的次数,从而提高性能。如果你的程序的内存占用基本都会在某个阈值之下的话,这个技术非常有效,毕竟,Go很大的一部分性能消耗都是在GC上。这是twitch.tv的工程师提供的一种技术。
  • GOGC tuner[2]: 通过自动调整GOGC,来动态的调整GC的target,用来在内存足够的时候调整GOGC来减少GC的次数,这也是一个非常有趣有效的技术,在uber公司的实践中行之有效。这是uber工程师提供的一项技术,Uber的工程师并没有把它开源出来,不过曹大根据文章的原理实现了一个cch123/gogctuner[3]。

现在, Go 1.19 提供了 SetMemoryLimit的功能,通过这个方法,可以替换 ballast的方案,部分替换 GOGC Tuner的方案。

谈起这个功能的历史,可以追溯到2017年12月的#23044[4],它提议增加一个方法,可以指定最小的目标堆大小。这个issue大家讨论的热火朝天,结果就是2019年twitch.tv的工程师实现了ballast,从工程的角度验证了GC是可以优化,而且在实践中也有效。

2021年Go team的工程师 Michael Knyszek 发起一个提案#44309[5],包括设计文档user configurable memory target[6]。这个提案的跟踪issue最终归于#48409[7]。

本来,这个提案预期在Go 1.18中实现,不过因为提案迟迟没有批准,所以最终会在Go 1.19中实现。

在撰写本文的时候,Go 1.19还在开发之中,不过这个提案的功能已经实现,剩下的是一些文档和bug修复的工作了,所以我们可以使用gotip[8]来测试。

这个提案的实现原来就是要实现(替换)ballast的功能,所以一旦Go 1.19发布, ballast的方案就可以废弃了。没想到今年突然Uber的工程师来了一个自动调整GOGC的方案,所以当前方案还不能完全代替GOGC tuner, 毕竟GOGC Tuner可以更灵活的调整GC的target,而 SetMemoryLimit在设定的 MemoryLimit之下,还是会频繁的进行GC, 如果加上 GOGC=off的话,只能等待达到 MemoryLimit才能GC,和GOGC Tuner的方式还有有所不同的,所以并不能完全替代GOGC tuner。

详细的 GC调优指导的官方文档[9]还没有完成,大家也可以关注一下,看看官方的建议。

This page is currently a work-in-progress and is expected to be complete by the time of the Go 1.19 release. See this tracking issue[10] for more details.

即使官方文档还没有完成,依照提案的内容,我们还是可以早点了解这个提案的功能以及带给我们的收益。

下面通过四个场景,观察一下此功能对GC的影响:

  • SetMemoryLimit + GOGC=off + MemoryLimit足够大
  • SetMemoryLimit + GOGC=off + MemoryLimit不足够大
  • SetMemoryLimit + GOGC=100 + MemoryLimit足够大
  • SetMemoryLimit + GOGC=100 + MemoryLimit不足够大

基本例子

本文通过Debian的benchmarks game中的btree例子[11]演示这四个场景。

因为这个例子会频繁生成生成二叉树,正适合内存分配和回收的场景。

package&#xA0;main<br><br>import&#xA0;(<br>&#xA0;"flag"<br>&#xA0;"fmt"<br>&#xA0;"sync"<br>&#xA0;"time"<br>)<br><br>type&#xA0;node&#xA0;struct&#xA0;{<br>&#xA0;next&#xA0;*next<br>}<br><br>type&#xA0;next&#xA0;struct&#xA0;{<br>&#xA0;left,&#xA0;right&#xA0;node<br>}<br><br>func&#xA0;create(d&#xA0;int)&#xA0;node&#xA0;{<br>&#xA0;if&#xA0;d&#xA0;==&#xA0;1&#xA0;{<br>&#xA0;&#xA0;return&#xA0;node{&next{node{},&#xA0;node{}}}<br>&#xA0;}<br>&#xA0;return&#xA0;node{&next{create(d&#xA0;-&#xA0;1),&#xA0;create(d&#xA0;-&#xA0;1)}}<br>}<br><br>func&#xA0;(p&#xA0;node)&#xA0;check()&#xA0;int&#xA0;{<br>&#xA0;sum&#xA0;:=&#xA0;1<br>&#xA0;current&#xA0;:=&#xA0;p.next<br>&#xA0;for&#xA0;current&#xA0;!=&#xA0;nil&#xA0;{<br>&#xA0;&#xA0;sum&#xA0;+=&#xA0;current.right.check()&#xA0;+&#xA0;1<br>&#xA0;&#xA0;current&#xA0;=&#xA0;current.left.next<br>&#xA0;}<br>&#xA0;return&#xA0;sum<br>}<br><br>var&#xA0;(<br>&#xA0;depth&#xA0;=&#xA0;flag.Int("depth",&#xA0;10,&#xA0;"depth")<br>)<br><br>func&#xA0;main()&#xA0;{<br>&#xA0;flag.Parse()<br><br>&#xA0;start&#xA0;:=&#xA0;time.Now()<br>&#xA0;const&#xA0;MinDepth&#xA0;=&#xA0;4<br>&#xA0;const&#xA0;NoTasks&#xA0;=&#xA0;4<br>&#xA0;maxDepth&#xA0;:=&#xA0;*depth<br><br>&#xA0;longLivedTree&#xA0;:=&#xA0;create(maxDepth)<br><br>&#xA0;stretchTreeCheck&#xA0;:=&#xA0;""<br>&#xA0;wg&#xA0;:=&#xA0;new(sync.WaitGroup)<br>&#xA0;wg.Add(1)<br>&#xA0;go&#xA0;func()&#xA0;{<br>&#xA0;&#xA0;stretchDepth&#xA0;:=&#xA0;maxDepth&#xA0;+&#xA0;1<br>&#xA0;&#xA0;stretchTreeCheck&#xA0;=&#xA0;fmt.Sprintf("stretch&#xA0;tree&#xA0;of&#xA0;depth&#xA0;%d\t&#xA0;check:&#xA0;%d",<br>&#xA0;&#xA0;&#xA0;stretchDepth,&#xA0;create(stretchDepth).check())<br>&#xA0;&#xA0;wg.Done()<br>&#xA0;}()<br><br>&#xA0;results&#xA0;:=&#xA0;make([]string,&#xA0;(maxDepth-MinDepth)/2+1)<br>&#xA0;for&#xA0;i&#xA0;:=&#xA0;range&#xA0;results&#xA0;{<br>&#xA0;&#xA0;depth&#xA0;:=&#xA0;2*i&#xA0;+&#xA0;MinDepth<br><br>&#xA0;&#xA0;n&#xA0;:=&#xA0;(1&#xA0;<< (maxdepth - depth + mindepth))   notasks<br><br>&#xA0;&#xA0;tasks&#xA0;:=&#xA0;make([]int,&#xA0;NoTasks)<br>&#xA0;&#xA0;wg.Add(NoTasks)<br>&#xA0;&#xA0;//&#xA0;&#x6267;&#x884C;NoTasks&#x4E2A;goroutine,&#xA0;&#x6BCF;&#x4E2A;goroutine&#x6267;&#x884C;n&#x4E2A;&#x6DF1;&#x5EA6;&#x4E3A;depth&#x7684;tree&#x7684;check<br>&#xA0;&#xA0;//&#xA0;&#x4E00;&#x5171;&#x662F;n*NoTasks&#x4E2A;tree,&#x6BCF;&#x4E2A;tree&#x7684;&#x6DF1;&#x5EA6;&#x662F;depth<br>&#xA0;&#xA0;for&#xA0;t&#xA0;:=&#xA0;range&#xA0;tasks&#xA0;{<br>&#xA0;&#xA0;&#xA0;go&#xA0;func(t&#xA0;int)&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;check&#xA0;:=&#xA0;0<br>&#xA0;&#xA0;&#xA0;&#xA0;for&#xA0;i&#xA0;:=&#xA0;n;&#xA0;i&#xA0;>&#xA0;0;&#xA0;i--&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;check&#xA0;+=&#xA0;create(depth).check()<br>&#xA0;&#xA0;&#xA0;&#xA0;}<br>&#xA0;&#xA0;&#xA0;&#xA0;tasks[t]&#xA0;=&#xA0;check<br>&#xA0;&#xA0;&#xA0;&#xA0;wg.Done()<br>&#xA0;&#xA0;&#xA0;}(t)<br>&#xA0;&#xA0;}<br><br>&#xA0;&#xA0;wg.Wait()<br>&#xA0;&#xA0;check&#xA0;:=&#xA0;0&#xA0;//&#xA0;&#x603B;&#x68C0;&#x67E5;&#x6B21;&#x6570;<br>&#xA0;&#xA0;for&#xA0;_,&#xA0;v&#xA0;:=&#xA0;range&#xA0;tasks&#xA0;{<br>&#xA0;&#xA0;&#xA0;check&#xA0;+=&#xA0;v<br>&#xA0;&#xA0;}<br>&#xA0;&#xA0;results[i]&#xA0;=&#xA0;fmt.Sprintf("%d\t&#xA0;trees&#xA0;of&#xA0;depth&#xA0;%d\t&#xA0;check:&#xA0;%d",<br>&#xA0;&#xA0;&#xA0;n*NoTasks,&#xA0;depth,&#xA0;check)<br>&#xA0;}<br><br>&#xA0;fmt.Println(stretchTreeCheck)<br><br>&#xA0;for&#xA0;_,&#xA0;s&#xA0;:=&#xA0;range&#xA0;results&#xA0;{<br>&#xA0;&#xA0;fmt.Println(s)<br>&#xA0;}<br><br>&#xA0;fmt.Printf("long&#xA0;lived&#xA0;tree&#xA0;of&#xA0;depth&#xA0;%d\t&#xA0;check:&#xA0;%d\n",<br>&#xA0;&#xA0;maxDepth,&#xA0;longLivedTree.check())<br><br>&#xA0;fmt.Printf("took&#xA0;%.02f&#xA0;s",&#xA0;float64(time.Since(start).Milliseconds())/1000)<br>}<br><br></ (maxdepth - depth + mindepth)) >

可以使用 gotip build main.go生成Go 1.19编译的二进制文件。

后面的例子中我并没有使用 debug.SetMemoryLimit设置 MemoryLimit,而是使用环境变量 GOMEMLIMIT

SetMemoryLimit + GOGC=off + MemoryLimit 足够大

首先使用 gotip build main.go编译出可执行的二进制文件 soft_memory_limit

运行 GOMEMLIMIT=10737418240 GOGC=off GODEBUG=gctrace=1 ./soft_memory_limit -depth=21查看效果:

Go 1.19中终于实现了SetMemoryLimit的功能

这里我设置的 MemoryLimit为10G,整个程序中并没有达到这个内存阈值,所以没有GC发生。

是不是和设置ballast的效果一样。

SetMemoryLimit + GOGC=off + MemoryLimit 不足够大

我们将 MemoryLimit设置为1G,看看GC的表现(GOMEMLIMIT=1073741824 GOGC=off GODEBUG=gctrace=1 ./soft_memory_limit -depth=21):

Go 1.19中终于实现了SetMemoryLimit的功能

可以看到程序的运行过程内存占用还是能够触达阈值1G的,这会导致几次的垃圾回收,整体运行时间和case1差别不到,原因是GC回收仅仅几次,可以忽略。

如果你把阈值设置更小,比如缩小10倍(GOMEMLIMIT=107374182 GOGC=off GODEBUG=gctrace=1 ./soft_memory_limit -depth=21),可以看到更频繁的垃圾回收,程序整体运行时间也显著增加:

Go 1.19中终于实现了SetMemoryLimit的功能

SetMemoryLimit + GOGC=100 + MemoryLimit 足够大

为了达到ballast的效果,前面的case都把GOGC设置为了 off,如果我们设置为默认值100呢?

GOMEMLIMIT=10737418240 GOGC=100 GODEBUG=gctrace=1 ./soft_memory_limit -depth=21

Go 1.19中终于实现了SetMemoryLimit的功能

可以看到,会有大量的GC事件,并且很多并没有达到阈值就发生GC了。这也是显而易见的,因为在没有达到 MemoryLimit阈值的情况下,还是遵循GOGC的target决定要不要进行垃圾回收。

在这种情况下,可以使用GOGC tuner进行调优,避免这么多次的垃圾回收。

SetMemoryLimit + GOGC=100 + MemoryLimit 不足够大

如果设置的 MemoryLimit不足够大,在内存触达 MemoryLimit的时候也会触发GC,只不过因为没有关闭GOGC,所以GOGC和触达 MemoryLimit两种情况下都有可能触发GC,程序整体运行还是比较慢的。

Go 1.19中终于实现了SetMemoryLimit的功能

综上所述,通过 SetMemoryLimit设置一个较大的值,再加上 GOGC=off,可以实现ballast的效果。

但是在没有关闭 GOGC的情况下,还是有可能会触发很多次的GC,影响性能,这个时候还得GOGC Tuner调优,减少触达 MemoryLimit之前的GC次数。

参考资料

[1]

ballast: https://blog.twitch.tv/en/2019/04/10/go-memory-ballast-how-i-learnt-to-stop-worrying-and-love-the-heap/

[2]

GOGC tuner: https://eng.uber.com/how-we-saved-70k-cores-across-30-mission-critical-services/

[3]

cch123/gogctuner: https://github.com/cch123/gogctuner

[4]

23044: https://github.com/golang/go/issues/23044

[5]

44309: https://github.com/golang/go/issues/44309

[6]

user configurable memory target: https://github.com/golang/proposal/blob/7f0d01687e030f21e8bdc36dfd9d5aac3a6f4a71/design/44309-user-configurable-memory-target.md

[7]

48409: https://github.com/golang/go/issues/48409

[8]

gotip: https://pkg.go.dev/golang.org/dl/gotip

[9]

官方文档: https://tip.golang.org/doc/gc-guide

[10]

tracking issue: https://tip.golang.org/doc/gc-guide#:~:text=This%20page%20is%20currently%20a%20work%2Din%2Dprogress%20and%20is%20expected%20to%20be%20complete%20by%20the%20time%20of%20the%20Go%201.19%20release.%20See%20this%20tracking%20issue%20for%20more%20details.

[11]

btree例子: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/binarytrees-go-2.html

Original: https://www.cnblogs.com/ExMan/p/16393484.html
Author: ExplorerMan
Title: Go 1.19中终于实现了SetMemoryLimit的功能

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/545986/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 关于IDEA中Tomcat中文乱码的解决方案

    进入Tomcat/config文件夹下,打开编辑logging.properties 然后查看该文件内是否存在 java.util.logging.ConsoleHandler.e…

    技术杂谈 2023年7月23日
    075
  • 值得被提拔的人,往往具备以下几点特质

    管理阶层的养成,对一家企业来说至关重要。不仅可以保持企业的创造力,建立积极的企业文化,还可以吸引更多优秀的伙伴加入到团队中来。而值得被提拔的人,往往具备这几点特质。 作者:Mr.K…

    技术杂谈 2023年5月31日
    082
  • 好了歌 西江月

    好了歌 朝代:清代 |作者:曹雪芹 世人都晓神仙好,惟有功名忘不了!古今将相在何方?荒冢一堆草没了,世人都晓神仙好,只有金银忘不了!终朝只恨聚无多,及到多时眼闭了,世人都晓神仙好,…

    技术杂谈 2023年5月31日
    077
  • python练习题:将列表中的大写字母转换成小写

    将列表中的大写字母转换成小写如果list中既包含字符串,又包含整数,由于非字符串类型没有lower()方法,L1 = [‘Hello’, ‘World’, 18, ‘Apple’,…

    技术杂谈 2023年7月24日
    070
  • conda创建源报错HTTPError: 404 Client Error:

    问题分析 原来的镜像源不能使用。 解决方法 记事本打开路径C:\Users\Administrator下的.condarc文件,修改设置后如图 Original: https://…

    技术杂谈 2023年7月10日
    052
  • C++ 标准库 std::atomic 及 std::memory_order

    C++ 标准库提供了原子操作。(我已经懒得写序言了) ==================================== 先来说原子操作的概念: 原子操作是多线程当中对资源进…

    技术杂谈 2023年6月21日
    088
  • linuxvscodeextensionC#`GLIBC_2.27’notfound

    settings中omnisharp:useModernNet改为true reboot虚机 posted @2022-05-05 11:15 chester·chen 阅读(84…

    技术杂谈 2023年7月24日
    0103
  • Oracle多租户架构之如何快速创建一个PDB

    Oracle自从12c版本开始引入多租户的架构,整个管理理念也发生了很大的变化。比如之前再小的业务只要选择了Oracle,DBA都会选择新建一套独立的数据库,因为传统的架构只能在s…

    技术杂谈 2023年5月31日
    089
  • nvm的使用

    nvm是node的版本管理工具 在windows下安装nvm-windows,下载地址 https://github.com/coreybutler/nvm-windows/rel…

    技术杂谈 2023年5月31日
    088
  • 老生常谈系列之Aop–JDK动态代理的底层实现原理

    老生常谈系列之Aop–JDK动态代理的底层实现原理 前言 在Aop系列里面有两篇文章,分别是老生常谈系列之Aop–Spring Aop原理浅析和老生常谈系列…

    技术杂谈 2023年7月25日
    079
  • grep

    grep &#x57FA;&#x672C;&#x5339;&#x914D;&#xFF1A; grep a*re hello.txt –* …

    技术杂谈 2023年7月11日
    043
  • 用DirectX实现多视图渲染

    什么是多视图 一般的3D程序都只有一个视图,对应整个窗口的客户区。多视图就是在一个窗口中放置多个视图,以便从不同的角度观察模型或者场景。很多图形软件都有这个功能,比如大家熟知的3D…

    技术杂谈 2023年5月31日
    060
  • 工业软件技术的总结和开发方向

    以前总结了一回工业应用的技术栈方向,生成了一个技术导图已经做了罗列规划,内容也基本上包含了普通应用所需要的大部分方面,当然可能对于个人的技术见识来说会有遗漏空缺,这个还需要到具体项…

    技术杂谈 2023年7月23日
    084
  • jest beforeEach 和beforeAll区别

    写测试的时候,我们经常需要进行测试之前做一些准备工作,和在进行测试后需要进行一些整理工作。Jest提供辅助函数来处理这个问题。 为多次测试重复设置如果你有一些要为多次测试重复设置的…

    技术杂谈 2023年5月30日
    096
  • bootstrap响应式前端页面

    bootstrap响应式学习参考源码,代码主要是通过bootstrap实现了响应式布局,简单易懂。 html;gutter:true 一、项目目录</p> <pr…

    技术杂谈 2023年5月31日
    088
  • 3分钟快速了解猪齿鱼权限

    猪齿鱼 Choerodon 数智化开发管理平台 ,提供协作、测试、DevOps及容器等工具,帮助企业拉通软件开发和项目管理的需求、设计、开发、部署、测试和运营全流程,全面满足企业研…

    技术杂谈 2023年7月24日
    096
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球