go - 为什么 CGO_ENABLE 会对虚拟内存产生如此大的影响?
问题描述
我有一个用 Golang 编写的小守护进程,它循环工作并做一些事情。我发现,在使用 CGO_ENABLE=1 或 CGO_ENABLED=0 编译的情况下,守护程序的行为会有所不同。例如,在 CGO_ENABLE=1(默认值)的情况下,程序的 VSZ 在短时间内(一小时内)膨胀到 1-2GB。当 CGO_ENABLED=0 时,VSZ 在很长一段时间内(几天)是相同的。看看下面的数字:
CGO_ENABLED=1(守护进程工作了 5 分钟)
$ grep -E 'VmSize|VmRSS' /proc/14916/status
VmSize: 1084052 kB
VmRSS: 12524 kB
CGO_ENABLED=0(守护进程工作了约 30 小时)
$ grep -E 'VmSize|VmRSS' /proc/15160/status
VmSize: 110232 kB
VmRSS: 9756 kB
守护进程不使用依赖于 CGO 的包或函数。其他 Go 编写的程序表现出相同的行为。我知道 VSZ 和 RSS 之间的区别,我很感兴趣这种行为的本质是什么?为什么用 CGO_ENABLED=1 编译的程序要求从内核提供这么多内存?
我更喜欢不是“别担心,VSZ 只是一个虚拟内存,实际上它不被进程使用”形式的答案。
解决方案
我可以做出有根据的猜测。
您可能知道,“参考”Go 实现(历史上称为“gc”;可从主站点下载)的编译器默认生成静态链接的二进制文件。这意味着,此类二进制文件仅依赖于操作系统内核提供的所谓“系统调用”,而不依赖于操作系统(或第 3 方)提供的任何共享库。
在基于 Linux 的平台上,这并不完全正确:在默认设置中(在 Linux 上为 Linux 构建,即不交叉编译),生成的二进制文件实际上与libc
和与libpthread
(间接地,通过libc
)链接。
这种“扭曲”源于 Go 标准库必须与操作系统交互的两个需求:
net
软件包需要的 DNS 解析。- 包需要的用户和组查找
os
。
这里的问题有两个:
Linux本身(即内核,而不是整个操作系统)不提供任何方法来执行这些任务。
任何典型的类 UNIX 系统,自古以来,都使用称为“NSS”的特殊工具来提供这两项任务,即“名称服务开关”¹。
NSS 提供了可插入模块,这些模块可以用作提供特定类型查询的数据库:DNS、用户/组数据库等(例如“服务”的知名名称等)。用户/组数据库的非标准提供程序的一个相当常见的示例是联系 LDAP 服务器的本地服务。
在典型的基于 GNU/Linux 的操作系统上,NSS 是由以下方式实现的
libc
(在不太典型的系统上,它可能由单独的共享库提供,但这并没有太大变化)。
因为 - 再一次,通常 -libc
就其 API 而言,它是一个相当稳定的库(它甚至提供版本化符号以适应未来),Go 作者正确地决定链接libc
以导入符号的最小子集(主要是getaddrinfo
, getnameinfo
,getpwnam_r
等)默认情况下是可以完成的,因为它对于 99% 的情况是安全的,如果不是,那些必须处理这些情况的人通常知道无论如何要做什么。
因此,默认情况下cgo
启用并用于使用 NSS 实现这些查找。
如果cgo
禁用,Go 编译器会链接到它自己的回退实现,它试图模仿成熟 NSS 实现的子集(即解析/etc/resolv.conf
并使用其中的信息直接查询此处列出的 DNS 服务器;解析/etc/passwd
并/etc/group
服务用户/组数据库查询)。
如您所见,在默认情况下,
- 被
libc
映射进来,并且 - 它被初始化并使用一些内存来满足自己的需要——例如明显缓存 NSS 调用返回的数据。
反之,当cgo
被禁用的情况下,以上两件事都不会发生。您有更多静态链接的 stdlib 代码,但看起来默认情况仅在整体累积 RSS 使用方面胜过后一种情况。
考虑研究 此查询的输出以 获得更多乐趣;-)
¹ 不要与 Mozilla 的libnss
.
推荐阅读
- laravel - Laravel-如何使用 Redis 缓存自定义查询
- kubernetes - 无法列出除已完成之外的任何状态的 pod
- odoo - 如何在多个网站 Odoo 之一中显示我的模块?
- java - 如何使用 Bazel 运行所有 JUnit 测试
- javascript - 上下文 API 将状态提升到更高的组件
- php - Woocommerce 在结帐页面上删除查看购物车
- java - 如何删除 csv 文件中的一行?
- xml - XSLT 转换将 xml 数据转换为名称值对
- python - FPS 值在 VideoWriter 方法中不被理解为十进制或整数值
- express - Firebase nodejs函数设置cookie TypeError:选项sameSite无效