Skip to content

Binary size and startup RSS: --closed saves 0.85%, the cited dependency attribution is ~3x overstated, and we use 78 MB to print 1 #690

Open

Description

@tannevaled

Two related measurements, both of which contradict what we have been saying about
this binary — including in a PR body I wrote today.

1. --closed is not a size feature: it saves 0.85%

build size
ordinary rbgo 173.7 MiB
rbgo build --closed (after #684 drops the RuboCop binding) 172.2 MiB

Dropping the entire front-end — lexer, parser, compiler, 255 go-ruby-parser
symbols — saves 1.5 MiB of 173.7. go list -deps -tags rbgo_closed confirms
no parser, no rubocop and no compiler remain, so this is not a missing edge; the
front-end simply is not what makes the binary large. The integration test's
"closed should be smaller than open" assertion holds, but it was never measuring
much.

2. What actually occupies it — measured, not attributed

Segment sizes (size -m), which is the only thing that answers "what is in the
file":

__text (code) 54.7 MB
__gopclntab (Go's PC-line table) 45.5 MB
__DATA_CONST.__rodata 13.0 MB
type metadata (typerel.* + _type:*) 22.2 MB
go:func.* + go:string.* 11.0 MB
__noptrbss 33.7 MB — zerofill, occupies no file space

So the bulk is code plus Go's own runtime and reflection metadata, not any
one dependency.

The attribution we had was wrong, and wrong in an instructive way. A comment
in backends_wasm.go names "Arrow 23 MB, go-redis 7 MB, franz-go 6.7 MB", and
that comment has been quoted as the explanation. Measured against the actual
symbol table:

dependency cited in the comment measured
Arrow 23 MB 7.8 MB
go-redis 7 MB 3.1 MB
franz-go 6.7 MB 2.1 MB

Roughly 3× overstated, and together 13 MB of 173.7 rather than 37. The comment
should be corrected or dated, because it is being read as a measurement.

3. The number that actually matters for an embeddable VM: 78 MB of RSS to print 1

max RSS, -e 'puts 1'
MRI 4.0.5 18.3 MB
rbgo 78.4 MB (4.3×)

For something whose pitch is "embed a Ruby in your Go program", resident memory
at startup is a first-class number and we do not track it anywhere.

A hypothesis I had and disproved, recorded so nobody re-runs it: the symbol
table has a single 32.00 MB BSS entry, crypto/internal/fips140/drbg.memory,
which looks like an obvious culprit. It is not — controlled with GODEBUG:

max RSS
default 78.4 / 77.7 MB
GODEBUG=fips140=off 80.2 / 77.9 MB — unchanged
GODEBUG=fips140=on 112.3 / 111.1 MB

So that buffer is not resident unless FIPS mode is explicitly enabled, where it
costs +34 MB. It explains nothing about the default 78 MB, and the remaining 60 MB
over MRI is unaccounted for and worth profiling.

Suggested follow-ups

  • Correct or date the backends_wasm.go attribution comment.
  • Track binary size and startup RSS as recorded numbers, since both are
    currently claimed rather than measured.
  • If size matters, the levers are the gem bindings in aggregate and Go's
    metadata (-ldflags="-s -w" is worth measuring), not the front-end.
  • Note for anyone measuring this: go tool nm -size lists U (undefined,
    dynamically-linked) symbols with a sentinel size of 4294971392 — 4 GB. Summing
    sizes without filtering by symbol type produces nonsense that looks plausible.
    Filter to T/D/B/R.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions