chore: Added notes about how to evaluate binary size.

This commit is contained in:
Spencer Brower
2026-08-30 13:31:40 -04:00
parent 7a8173832e
commit 814b1c9996
2 changed files with 98 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
# Visualizing & Managing `thor` Binary Size
How to inspect what makes up the compiled `thor` binary, and how to keep that
inspection useful once we bundle layout/asset files into the binary.
## Quick inspection (tools already available)
The binary is a not-stripped ELF, so it ships symbols we can break down.
```bash
# Section totals (text / data / bss)
size ./thor
# Top symbols by size
nm --print-size --size-sort --radix=d ./thor \
| awk '{sz=$2+0; sub(/^[0-9a-fA-F]+ +[0-9]+ +[A-Za-z] +/,""); printf "%8d %s\n", sz, $0}' \
| sort -rn | head -25
# Aggregate symbol sizes by Odin module namespace (foo::bar -> "foo")
nm --print-size --size-sort --radix=d ./thor \
| awk '{sz=$2+0; rest=$0; sub(/^[0-9a-fA-F]+ +[0-9]+ +[A-Za-z] +/,"",rest);
ns=rest; if (rest ~ /::/){sub(/::.*/,"",ns)}
else if (rest ~ /^ts_/){ns="treesitter (ts_*)"} else {ns="<other/runtime>"};
s[ns]+=sz} END{for (k in s) printf "%9d %s\n", s[k], k}' \
| sort -rn | head -25
```
As of this writing the top contributors were: tree-sitter tables (~118 KB),
`main` (~96 KB), runtime/other (~93 KB), `mustache` (~67 KB), then `fmt` /
`encoding_json` from the stdlib.
## Better visualization: `bloaty`
`bloaty` (Google's binary-size profiler) is the right tool. Not installed, but
runnable via Nix:
```bash
bloaty ./thor # by section
bloaty ./thor -d symbols -n 30 # by symbol, hierarchical
bloaty ./thor -d sections,symbols # section then symbols within each
# via nix (no install):
nix run nixpkgs#bloaty -- ./thor -d symbols -n 30
```
For a stripped-size estimate:
```bash
readelf -S ./thor | grep -E 'debug|symtab|strtab' # strippable overhead
strip -s ./thor -o thor.stripped && size thor.stripped
```
Odin also supports smaller builds: `odin build . -o:size -no-bounds-check`.
## IMPORTANT: keeping bloaty useful once we bundle layouts into the binary
Today the VFS (`vfs.odin`) reads layout/asset files lazily from disk at runtime
(`VFS_Entry.fs_path` + `os.read_entire_file_from_path`); `VFS_Entry.data` is
never populated at build time. So embedded content is **not** in `./thor` yet.
When we switch to embedding via Odin `#load(...)`, the file bytes land in the
binary's `.rodata` section. Whether bloaty can show *per-file* sizes depends on
how we emit the embed:
- **Section total — always visible.** `.rodata` grows by ~the total bundled
size. `bloaty ./thor -d sections` (or `size`) shows the aggregate, and we can
diff before/after. This always works.
- **Per-file breakdown — only if each file is a NAMED symbol.**
- A generated `map[string]VFS_Entry` literal full of inline `#load(...)`
expressions makes each blob an **anonymous** `.rodata` constant. Bloaty
lumps them together — no per-file breakdown.
- Assigning each file to its own **package-level variable** makes each a named
global symbol, so `bloaty ./thor -d symbols` (and the `nm` recipe above)
show each file individually:
```odin
layout_base_html := #load("defaults/layouts/base.html")
layout_single_html := #load("defaults/layouts/single.html")
// ...then reference these in the VFS map instead of inlining #load.
```
### Decision to bake into the bundling codegen
When we build the codegen that replaces the runtime `mount_dir` disk walk, have
it emit **one named package-level variable per embedded file** (not an anonymous
map literal). That single choice is what makes bloaty / `nm` per-file analysis
possible — cheap to do up front, painful to retrofit.
Additional options that help regardless of symbolization:
1. Build with Odin `-debug` (DWARF) and use `bloaty ./thor -d compileunits` to
attribute embedded bytes to the generated source file.
2. Since the generator already knows every file and its size, emit a size
manifest at bundle time (`path -> bytes`, sorted / treemap). This is the most
reliable per-file view and can't drift from reality; bloaty then just
confirms the `.rodata` total matches.