From 814b1c99961c99011f27133f235083bb724aa9a8 Mon Sep 17 00:00:00 2001 From: Spencer Brower <6729162+sbrow@users.noreply.github.com> Date: Sun, 30 Aug 2026 13:12:02 -0400 Subject: [PATCH] chore: Added notes about how to evaluate binary size. --- AUDITING_BINARY_SIZE.md | 96 +++++++++++++++++++++++++++++++++++++++++ flake.nix | 2 + 2 files changed, 98 insertions(+) create mode 100644 AUDITING_BINARY_SIZE.md diff --git a/AUDITING_BINARY_SIZE.md b/AUDITING_BINARY_SIZE.md new file mode 100644 index 0000000..6cadc08 --- /dev/null +++ b/AUDITING_BINARY_SIZE.md @@ -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=""}; + 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. diff --git a/flake.nix b/flake.nix index 5195c5a..48fe67f 100644 --- a/flake.nix +++ b/flake.nix @@ -167,6 +167,8 @@ gdb perf + bloaty # du for binaries? + # IDE unstable.helix claude-code