I could make an educated guess.
As you probably know, the compiler of the "reference" Go implementation (historically dubbed "gc"; that one, available for download from the main site) by default produces statically-linked binaries. This means, such binaries rely only on the so-called "system calls" provided by the OS kernel and do not depend on any shared libraries provided by the OS (or 3rd parties).
On Linux-based platforms, this is not completely true: in the default setting (building on Linux for Linux, i.e., not cross-compiling) the generated binary is actually linked with libc
and with libpthread
(indirectly, via libc
).
This "twist" comes out of the two needs the Go standard library has to interact with the OS:
- DNS resolving, which is needed by the
net
package.
- User and group lookup, which is needed by the
os
package.
The problem here is two-fold:
The Linux itself (that is, the kernel, not the whole OS) does not provide any means to carry out those tasks.
-
Any typical UNIX-like system, since forever, provides for both those tasks using a special facility called "NSS",
which is the "Name-Service Switch"¹.
The NSS provides for pluggable modules which can serve
as the databases offering queries of particular type: DNS, user/group database, and more (such as well-known names for "services" etc). A supposedly rather common example of
a non-standard provider for the user/group databases is a local
service which contacts an LDAP server.
On a typical GNU/Linux-based OS the NSS is implemented by
libc
(on less typical systems it might be provided by a
separate shared library but this does not change much).
Since — again, typically, — the libc
is a rather stable
library in terms of its API (it even provides versioned symbols
to be future-proof), the Go authors rightfully decided that linking against libc
to import a minimal subset of symbols (mostly getaddrinfo
, getnameinfo
, getpwnam_r
etc) is OK
to be done by default as it's safe for 99% of cases,
and when it isn't, those who have to tackle these cases usually
know what to do anyway.
So, by default cgo
is enabled and used to implement these lookups using NSS.
If cgo
is disabled, the Go compiler instead links in its own
fallback implementations which try to mimic a subset of what a
full-blown NSS implementation does (i.e. parse /etc/resolv.conf
and use the information from it to directly query the DNS servers listed here; parse /etc/passwd
and /etc/group
to serve the user/group database queries).
As you can see, in the defult case,
- The
libc
gets mapped in, and
- It is initialized and uses some memory for its own needs —
such as obvious caching of the data the NSS calls return.
Conversely, in the case when cgo
is disabled, the above two things do not happen. You have more stdlib code linked in statically but looks like the default case merely trumps the latter one in terms of the overall cumulative RSS usage.
Consider studying the output of
this query
for additional fun ;-)
¹ not to be confused with Mozilla's libnss
.