Skip to content

perf(app): lazy-load subcommand modules to halve binary startup#17

Merged
zoltanf merged 1 commit into
mainfrom
perf/startup-under-200ms
May 20, 2026
Merged

perf(app): lazy-load subcommand modules to halve binary startup#17
zoltanf merged 1 commit into
mainfrom
perf/startup-under-200ms

Conversation

@zoltanf
Copy link
Copy Markdown
Owner

@zoltanf zoltanf commented May 20, 2026

Summary

  • Subcommand modules in rc0.commands.* are now imported on demand by a new LazyTyperGroup in src/rc0/app.py. rc0 --help and rc0 --version no longer pull in httpx, pydantic models, or the rich help formatter.
  • PyInstaller bundle still covers every subcommand via a new hook at packaging/pyinstaller-hooks/hook-rc0.py (uses collect_submodules), wired into both the local rc0.spec and the GitHub release workflow.
  • New tests/perf/test_startup.py (gated by RC0_PERF=1 + a built binary) enforces a 200ms median budget so future regressions fail CI.

Measured on Apple Silicon (warm, median of 7 runs)

  • rc0 --version: ~270ms → ~115ms
  • rc0 --help: ~310ms → ~155ms

Test plan

  • uv run ruff check . && uv run ruff format --check . && uv run mypy && uv run pytest — all green (459 passed, perf gated-skipped, coverage 90.48%)
  • PyInstaller build with the new hook bundles every subcommand
  • RC0_PERF=1 pytest tests/perf/test_startup.py against the built binary — both assertions pass
  • Exercised every introspected leaf-command --help against the binary (70/70 succeed; 2 pre-existing dnssec simulate cases identical in source vs. binary)
  • Live read-only smoke against the built binary via RC0=dist/rc0/rc0 SKIP_MUTATIONS=1 scripts/smoke-live.sh — 72 / 72 valid assertions pass (1 stale assertion in the smoke script around hoisted-flag ordering is pre-existing and unrelated; tracked as a follow-up)

LazyTyperGroup keeps `rc0.commands.*` out of the import graph until a
user actually runs a subcommand. Top-level help and version no longer
pay for httpx, pydantic models, or rich import overhead.

Measured on Apple Silicon (warm cold-start, median of 7 runs):

- `rc0 --version`: ~270ms -> ~115ms
- `rc0 --help`:    ~310ms -> ~155ms

`tests/perf/test_startup.py` (gated by `RC0_PERF=1` plus a built
binary) asserts both stay under 200ms so future regressions fail CI.

`packaging/pyinstaller-hooks/hook-rc0.py` runs `collect_submodules`
over `rc0.commands` so PyInstaller (which can't see
`importlib.import_module` strings) still bundles every subcommand.
The CI release workflow and the local `rc0.spec` both point at this
hooks dir.

`introspect` walks via `list_commands`/`get_command` so the
JSON schema still enumerates lazily-registered subcommands.
@zoltanf zoltanf merged commit 4ede0db into main May 20, 2026
5 checks passed
@zoltanf zoltanf deleted the perf/startup-under-200ms branch May 20, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant