0x01 最简单的Go程序

package main

import (
"fmt"
"time"
) func main() {
fmt.Println("hello")
for {
// sleep一下,避免CPU占用过高。
time.Sleep(1 * time.Second)
}
}

不含调试符号的二进制大小为1229464Byte,约1.2MiB。

编译出二进制,同样通过readelf工具检查下。看到Program Header中有3个可加载段。

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x0815e7 0x0815e7 R E 0x1000
LOAD 0x082000 0x0000000000482000 0x0000000000482000 0x091cd0 0x091cd0 R 0x1000
LOAD 0x114000 0x0000000000514000 0x0000000000514000 0x017ea0 0x0497f0 RW 0x1000

根据Flg可以判断分别对应代码、常量、变量。内存大小分别为:517.47KiB、583.20KiB、293.98KiB。

大约是1.2MiB。

Go的堆和通常意义的进程堆不是等同的。大小关系为

Go堆 < 进程堆

这个分析的目的是:

  1. 找出Go进程堆除了Go堆,还包含了哪些内容?
  2. 为什么看到的Go进程的RSS值,远大于Go的堆?

0x02 详细分析

再通过readelf -W -S main查看,可以确认变量区保存了以下section

类型段见文档elf中解释的。

PROGRBITS:指完全由elf文件中的内容定义。

NOBITS:不占用文件的实际空间,但有真实的偏移。告诉加载器,不用真的去读文件内容。

Section 大小(Bytes) 类型 说明
.go.buildinfo 224 PROGBITS Go的构建信息
.noptrdata 67104 PROGBITS 非指针类型数据,大约65KiB
.data 30608 PROGBITS 可能是指针类型的数据,大约29KiB
.bss 183KiB NOBITS 未初始化全局静态,猜测是给Go runtime使用C栈部分使用的。
.noptrbss 14KiB NOBITS

除了C语言已有段,Go还有自己的自定义Section如下。

都包含在了常量段中。

段名 解释 备注
.typelink 类型链接段 modules中记录在types类型表中的偏移,或者是typemap中的key
.itablink 接口链接段 大小跟接口数量有关
.gosymtab 符号段 strip后,这个段为空
.gopclntab Go中用于栈回溯用到的pc和栈帧映射表,每个module有自己的表 程序代码越多,此段越大。

go中的module并不是package。go支持plugin,所以一般编译出来的程序只有一个module。

readelf看到的可加载的Section有12个,但查看main的/proc/<pid>/smap可以看到多达22个Section。

查看smap中的段

用途 来源 权限 对应Section
00400000-00482000 代码段 二进制中的代码 r-xp
00482000-00514000 常量段 二进制中的 r--p
00514000-0052c000 变量段 二进制中 rw-p .go.buildinfo .noptrdata .bss 一段
0052c000-0055e000 变量段 二进制中 rw-p .bss一段和noptrbss
c000000000-c000400000 根据寄存器的内容看是栈 rw-p
c000400000-c004000000 也是栈。 ---p
7fcce0548000-7fcce2800000 mspan
7fcce2800000-7fcce2c00000 mspan rw-p
7fcce2c00000-7fcce2c04000 mspan rw-p
7fcce2c04000-7fccf317d000 mspan ---p
7fccf317d000-7fccf317e000 mspan rw-p
7fccf317e000-7fcd0502d000 mspan ---p
7fcd0502d000-7fcd0502e000 mspan rw-p
7fcd0502e000-7fcd07403000 mspan rw-p
7fcd07403000-7fcd07404000 mspan rw-p
7fcd07404000-7fcd0787d000 mspan ---p
7fcd0787d000-7fcd0787e000 mspan rw-p
7fcd0787e000-7fcd078fd000 mspan ---p
7fcd078fd000-7fcd0795d000 mspan rw-p
7ffdd88fd000-7ffdd891f000 C栈和system栈 rw-p
7ffdd898c000-7ffdd8990000 vvar r--p
7ffdd8990000-7ffdd8992000 vdso r-xp

注:

  1. ---p类型的是从背后看但还没映射物理页的区域。
  2. mspan为笔者加的,实际看smaps内容,没有这样的标识。笔者通过dlv等手段判定的。mspan是Go中管理Go堆的数据结构。

Q:为什么mspan和堆区隔了比较大的空间?

A:mspan是通过指定mmap flag为 mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0)分配的。而堆区有严格的地址空间约束,是指定起始地址通过mmap向操作系统主动占用的。这样做的好处是可以很方便通过地址判断属于哪个内存区域。

待分配的空间通过runtime.mheap_.arenaHints(单向链表)管理的。1.20之后增加了userArena。通过dlv调试,可以确认c000000000-c000400000栈段是通过runtime.sysAlloc分配的,即指定起始地址的mmap方式分配的。分配之后就会从runtime.mheap_.arenaHints中移除掉。

通过dlv中看到的内容如下:

(dlv) p %x runtime.mheap_.arenaHints
*runtime.arenaHint {
_: runtime/internal/sys.NotInHeap {
_: runtime/internal/sys.nih {},},
addr: c004000000,
down: %!x(bool=false),
next: *runtime.arenaHint {
_: (*"runtime/internal/sys.NotInHeap")(0x7fcd079219c8),
addr: 1c000000000,
down: %!x(bool=false),
next: *(*runtime.arenaHint)(0x7fcd079219b0),},}

§ 0x03 小结

通过以上分析得到的Go进程的内存RSS占用组成如下:

总体来说,对Go内存影响最大的两个因素:

  1. 业务越复杂,代码、常量、全局变量占用的就越多,特别是常量区的.gopclntab增长最为明显。以kubectl为例,42MiB的二进制,这个段占据了大概12MiB。
  2. 业务并发越多,协程越多,Go堆的占用就越多。因为Go堆包含了用户协程栈。

§ 0x04 为什么Go进程RSS高?

另外,因为用Go开发大多是一些容器应用,要求这些程序静态编译,结果就是Go的代码段不可能与其他Go应用复用,所以Go的RSS会较高。

还有,因为Go自己实现了栈管理,所以回溯栈(打堆栈、GC场景)时需要依赖pclntab,这部分信息如上所述,也占据很大内存空间,同样不同Go程序也不能复用,所以更加剧了Go的内存占用。

从上述两个因素出发,如何优化内存占用呢?

4.1 减小代码段

动态编译后面社区大概率是不会支持了,见issue。所以想通过这种方式减少Go的代码段,不可行。

Go相比Python这种脚本语言有个问题,一个package下的源文件,即便里面的某个接口你用不到,只要你用到了其中一个,其他的源文件就被编译进二进制中。

如果可以识别哪部分占比大,同时又不用的话,就可以这样本地化之后,通过//go:build的构建标签来优化。查看二进制中哪个package贡献大小,可以使用如下的工具查看: https://github.com/goccy/go-graphviz

4.2 GC调优

另外一个思路就是GC调优。

虽然GO只有一个GOGC的调优参数,但关联较多,需要额外一个文档才能说清楚。

§ 0x05 参考

  1. mmap与brk区别 https://www.cnblogs.com/vinozly/p/5489138.html
  2. Go二进制可视化 https://github.com/goccy/go-graphviz

Go进程内存占用那些事(二)的更多相关文章

  1. Linux系统下输出某进程内存占用信息的c程序实现

    在实际工作中有时需要程序打印出某个进程的内存占用情况以作参考, 下面介绍一种通过Linux下的伪文件系统/proc 计算某进程内存占用的程序实现方法. 首先, 为什么会有所谓的 伪文件 呢. Linu ...

  2. Linux查看进程内存占用及内存使用情况

    LINUX进程内存占用查看方法(1)top可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令:$ top ...

  3. 监控Linux系统所选的服务所占进程内存占用

    [代码] #!/bin/bash #程序功能描述: # 监控系统所选的服务所占进程内存占用 #作者:孤舟点点 #版本:1.0 #创建时间:-- :: PATH=/bin:/sbin:/usr/bin: ...

  4. window 实用操作(结束已打开无法删除进程 内存占用)

    1.win7删除文件,文件夹或文件已在另一程序中打开:https://jingyan.baidu.com/article/e75057f2a41e88ebc91a8985.html 删除文件时,提示“ ...

  5. 查看LINUX进程内存占用情况

    可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...

  6. 查看LINUX进程内存占用情况(转)

    可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...

  7. 查看LINUX进程内存占用情况及启动时间

    可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1) top top命令是Linux下常用的性能分 ...

  8. linux 查看某个进程内存占用情况命令

    1.先用ps查询进程号 ps -aux|grep 进程名字 2.查看更详细的内存占比 cat /proc/3664/status 返回结果:(其中VmRSS为进程所占用的内存)

  9. linux查看进程内存占用

    ps -aux | grep xxx USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND 可以看到RSS ...

  10. 使用showmap分析android进程内存占用情况(转载)

    转自:http://my.oschina.net/shaorongjie/blog/105354 可以使用adb shell showmap pid查看一个进程的showmap,这对于我们来说非常有用 ...

随机推荐

  1. .NET项目中使用HtmlSanitizer防止XSS攻击

    .NET项目中使用HtmlSanitizer防止XSS攻击 前言 最近博客也是上线了留言板功能,但是没有做审核(太懒了),然后在留言的时候可以输入<script>alert('xss')& ...

  2. arm linux 移植 iperf3

    背景 新做的硬件需要有进行一些板级接口测试:关于网络的测试很多时候只是停留在 ping 通:能够使用就算了.不知道网络的丢包率,也不知道网络吞吐的性能. 因此,需要使用一些专业化的工具来进行测试:查阅 ...

  3. 【基础整理】Mapping representation 机器人所用地图种类及相关介绍

    参考与前言 本文主要介绍 建图 Mapping 方面的一些 基础知识介绍与相关下游任务使用 涉及知识较为基础,SLAM大佬们可以提前退出了 主要针对应用为移动机器人与物流无人驾驶车:提前申明:大部分文 ...

  4. Swin Transformer:最佳论文,准确率和性能双佳的视觉Transformer | ICCV 2021

    论文提出了经典的Vision Transormer模型Swin Transformer,能够构建层级特征提高任务准确率,而且其计算复杂度经过各种加速设计,能够与输入图片大小成线性关系.从实验结果来看, ...

  5. 震撼登场,全国产RK3588J工业核心板,让您的产品更具特色!八核2.4GHz!

    RK3588J全国产工业核心板10月正式出售如需预定,请与创龙科技联系. 更多详情,请登录创龙科技天猫旗舰店. 瑞芯微RK3568J.RK3588J技术交流QQ群:567208221,欢迎加入!

  6. 煤矿安全大模型:微调internlm2模型实现针对煤矿事故和煤矿安全知识的智能问答

    煤矿安全大模型----矿途智护者 使用煤矿历史事故案例,事故处理报告.安全规程规章制度.技术文档.煤矿从业人员入职考试题库等数据,微调internlm2模型实现针对煤矿事故和煤矿安全知识的智能问答. ...

  7. 全网最适合入门的面向对象编程教程:11 类和对象的Python实现-子类调用父类方法-模拟串口传感器和主机

    全网最适合入门的面向对象编程教程:11 类和对象的 Python 实现-子类调用父类方法-模拟串口传感器和主机 摘要: 本节课,我们主要讲解了在 Python 类的继承中子类如何进行初始化.调用父类的 ...

  8. 编程好帮手:通义灵码(TONGYI Lingma),是阿里云出品的一款基于通义大模型的智能编码辅助工具

    通义灵码(TONGYI Lingma),是阿里云出品的一款基于通义大模型的智能编码辅助工具,提供行级/函数级实时续写.自然语言生成代码.单元测试生成.代码注释生成.代码解释.研发智能问答.异常报错排查 ...

  9. PHP str_replace() 函数详解

    PHP str_replace() 函数详解 1.前言: str_replace() 函数以其他字符替换字符串中的一些字符(区分大小写). 该函数区分大小写.请使用 str_ireplace() 函数 ...

  10. 图的存储、创建、遍历、求最小生成树、最短路径(Java)

    带权无向图 存储结构 存储结构选用邻接表. 当一个图为稀疏图时,使用邻接矩阵法显然要浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储方法,大大减少了这种不必要的浪费. 当然,即使我们所处理的 ...