# go-size-analyzer: finding out what is actually inside a Go binary

> A Go program that reads a compiled binary and attributes its bytes to packages, sections and vendor trees, with a web treemap, a terminal UI and a binary diff mode. It answers the only question that matters when a Go artifact grows too large.

**Zxilly/go-size-analyzer** — A tool for analyzing the size of compiled Go binaries, offering cross-platform support, detailed breakdowns, and multiple output formats.

- Repository: https://github.com/Zxilly/go-size-analyzer
- Website: https://gsa.zxilly.dev/
- Stars: 2,185 · Forks: 44
- Language: Go
- License: AGPL-3.0
- Published: 2026-10-08 · Updated: 2026-10-08 · Language: en
- Canonical page: https://hysenlabs.com/projects/zxilly-go-size-analyzer

## Attributing bytes to packages rather than guessing at them

A Go binary has no obvious size story. The linker does most of the work and gives you little to hold on to, so the usual reaction to a large artifact is speculation about which library is responsible. go-size-analyzer replaces that with a measurement: it parses the executable, maps symbols back to their packages, and totals the bytes per package.

The README's own text mode example makes the shape obvious. Analyzing a docker-compose linux binary produces a table with a percent column, a name column, a size column and a type column, and the type column is where the tool earns its keep. Entries are labelled vendor, std or section, which separates two questions people conflate. The section rows are raw ELF sections like .rodata at 15.52 percent, .gopclntab at 8.92 percent and .strtab at 7.51 percent. Those are linker artifacts, not imports, and no amount of dependency pruning removes them. The vendor rows are packages, and those are the ones a dependency decision can touch.

The largest single line in that example is k8s.io/api at 17.37 percent, ahead of everything else. That is the kind of sentence a build engineer wants and cannot get from go tool nm, because nm lists symbols rather than attributing weight to whole package trees. The table also shows entries rounded to zero bytes at the bottom, packages present in the dependency graph that contributed nothing measurable, which is itself useful signal when you are deciding what to drop.

## Four installation routes and a real Go 1.27 requirement

Four routes are given, and the third one carries a requirement worth reading before you start:

```bash
brew install go-size-analyzer
scoop install go-size-analyzer
go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest
```

Homebrew on macOS and Linux, scoop on Windows, and a go install for everyone else. Prebuilt binaries are also linked from the releases page, and a packaging badge points at repology for distribution packages.

The note attached to the go install route is the kind of thing that saves an afternoon: building from source requires Go 1.27 or later because the project uses encoding/json/v2. The go.mod in the repository root agrees, declaring go 1.27.0. If your toolchain is older, the prebuilt binaries are the path, and the tool will happily analyze a binary built by any Go version you like, since it parses the output rather than needing to build it.

The repository layout suggests what this project is made of. The cmd/ directory holds the entry point, internal/ the analysis code, ui/ a separate frontend workspace with its own package management, testdata/ the binaries used as fixtures, docs/ the documentation, and .goreleaser.yaml the release configuration. The dependency list is itself informative: bubbletea, bubbles and lipgloss for the terminal interface, kong for flag parsing, a treemap library, go-macho for Mach-O parsing, and fork builds of Go's own symbol table packages under the ZxillyFork namespace.

## Three ways to read the result, and a fourth that is slower

The same analysis is presented four ways, which is generous for a tool this size. The web mode is the default path most people take:

```bash
gsa --web golang-compiled-binary
```

That starts a server on port 8080 and opens a treemap in the browser, where clicking a package expands it. Output formats listed in the README are text, json, html and svg, so the treemap can be exported as a standalone file or committed as a build artifact rather than only viewed live.

The terminal UI is the second path and needs no browser at all:

```bash
gsa --tui golang-compiled-binary
```

The third is the text table shown above, which is the mode you would use in a script or a pull request comment because it diffs cleanly.

The fourth is the WebAssembly build hosted at gsa.zxilly.dev, and here the README is refreshingly blunt about the cost: the browser version is much slower than the native one, normally around ten times the analysis time for the same binary, and it is only recommended for applications under about 30 MB. That limitation is architectural rather than fixable, since the binary has to cross the wasm boundary and the parser cannot run on multiple cores. For a 200 MB server binary, use the native build.

## Diff mode is the feature that turns inspection into a check

The interesting part of this project is not the treemap, which you can get elsewhere, but the diff mode, which compares two binaries and reports what grew:

```bash
gsa bin-linux-1.21-amd64 bin-linux-1.22-amd64
```

The output table has a percent column that is now signed, plus old size, new size and diff columns. A Go toolchain upgrade is a good test case, because Go's own internals shift between releases. The README example shows runtime growing by 230 kB, a 28.69 percent increase, and a package called internal/chacha8rand appearing from nothing at 3.1 kB. Both of those are exactly the kind of change that a size-conscious reviewer wants explained and nobody notices otherwise.

One limitation is stated in the feature list itself and worth repeating: diff mode supports json and text output. The full output format set of text, json, html and svg applies to single-binary analysis, so you cannot produce an HTML diff. For CI purposes that is rarely a problem, since the text table is what a log reader wants, but if your pipeline publishes an HTML report you will need two invocations.

A caveat applies to the whole tool. It reads finished binaries, so it can name the package that grew and not the import statement responsible. The step from that finding to an actual reduction still belongs to a dead code detector or a careful look at your own module graph.

## Version churn from Go releases and dependency bots

The release history shows a project shaped by two external forces. v1.14.0, published on 2026-08-21, is titled Support Go 1.27 and its substantive change is a single feature commit adding Go 1.27 support. v1.14.1 followed on 2026-09-27 with the note More fix for Go 1.27. v1.13.1 on 2026-08-07 covers Go 1.26.5.

So roughly every quarter a new Go release arrives and this project needs to teach its symbol table parser something new. That is a structural maintenance obligation, not a sign of trouble, and it is the same obligation the forked symbol packages in go.mod exist to absorb. Everything else in the release notes is dependabot traffic: react-dropzone, vite, undici, picomatch, postcss, Go module bumps. The UI is a real frontend workspace with its own dependency graph, and it needs the same attention.

The last push was on 2026-09-28, the repository is not archived, and the homepage is the same domain that serves the WebAssembly build. Licensing is the detail to look at before adopting it anywhere unusual: the project is AGPL-3.0, which is a network copyleft licence. Reading a binary is what the tool does, so the licence question is about whether you ship it inside something, and that is worth settling before you wire it into a hosted service rather than a developer laptop.

## Conclusion

go-size-analyzer earns a permanent place in a Go build pipeline because binary size regressions are the kind of problem nobody notices until a release review asks why the artifact grew by twenty megabytes, and by then the cause is a dependency tree nobody wants to unpick by hand. The text mode alone will answer most questions, and the diff mode is the feature that turns a one-off inspection into a repeatable check. Two things bound the tool. It reads finished binaries rather than source, so it can tell you that a vendor package is large but not which import to remove, and the WebAssembly build is slow enough that the README confines it to small programs. Install it with the go install route if your toolchain is 1.27 or newer, run text mode against a known large binary to learn the vocabulary, then wire diff mode into CI between two tags.

## FAQ

### How large is the Go runtime in a compiled binary?

It depends entirely on what you linked. In the README's own text mode output for a 63 MB docker-compose binary, the runtime package accounts for 884 kB, or 1.40 percent, sitting below net at 1.73 percent and crypto at 1.57 percent. Several internal packages in the same analysis land between 39 B and 6.6 kB, so the runtime is rarely the thing to optimise first. Running go-size-analyzer in text mode against your own artifact gives the figure for your build rather than a general one.

### How do I install go-size-analyzer on Windows or macOS?

On Windows the README gives scoop install go-size-analyzer, and on macOS or Linux it gives brew install go-size-analyzer. A third route works everywhere with Go on the PATH: go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest. That route needs Go 1.27 or later because the project uses encoding/json/v2, so on an older toolchain take a prebuilt binary from the releases page instead.

### Can go-size-analyzer compare two builds to find a size regression?

Yes, and this is what the diff mode is for. Pass two binary paths and the output table gains signed percent, old size, new size and diff columns, sorted so the largest growth appears first. The README example compares two toolchain builds and shows the runtime package growing by 230 kB between them. Diff output is available as text or json only, not as html or svg.

### How does go-size-analyzer differ from go tool nm?

go tool nm prints a symbol table, one address and size per symbol, and leaves you to aggregate. go-size-analyzer reads the same executable format and aggregates for you, grouping symbols into packages and labelling each row as vendor, std or section. That distinction matters because section rows such as .rodata and .gopclntab are linker artifacts that no dependency change will remove, and seeing them named separately stops you from hunting for an import that does not exist.

## Sources

- [License: AGPL-3.0](https://github.com/Zxilly/go-size-analyzer/blob/master/LICENSE)
- [Project website](https://gsa.zxilly.dev/)
- [README](https://github.com/Zxilly/go-size-analyzer/blob/master/README.md)
- [Releases](https://github.com/Zxilly/go-size-analyzer/releases)
- [Zxilly/go-size-analyzer on GitHub](https://github.com/Zxilly/go-size-analyzer)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zxilly-go-size-analyzer
