Skip to content

Add benchmark suite and fix core rendering hot-path inefficiencies - #183

Closed
berendkleinhaneveld wants to merge 1 commit into
masterfrom
perf/benchmark-suite-and-hotpath-fixes
Closed

Add benchmark suite and fix core rendering hot-path inefficiencies#183
berendkleinhaneveld wants to merge 1 commit into
masterfrom
perf/benchmark-suite-and-hotpath-fixes

Conversation

@berendkleinhaneveld

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a pytest-benchmark suite (bench/) covering mount cost, nested-component mount, attribute-update-at-depth, and keyed/unkeyed v-for reconciliation, mirroring the sibling observ repo's setup.
  • Adds .github/workflows/benchmark.yml: on PRs, runs the suite against master and against the PR branch, failing the job if mean time regresses >5%.
  • Fixes three hot-path inefficiencies in the core rendering code, each verified against the new benchmarks and the full test suite:
    • weak.py: the arity check no longer builds a full inspect.Signature per callback, just reads __code__.co_argcount. ~25-33% faster mounting of elements with dynamic binds.
    • ComponentFragment.mount(): replaces a hand-rolled BFS/deque with the already-existing first() DFS helper. Added a regression test for the wrapped-root case.
    • Fragment._component_parent(): now cached instead of walking the parent chain on every attribute update. Flattens update_at_depth benchmark scaling from ~5/7.2/15.5µs (depths 10/50/200) to a flat ~4.3µs. Includes an explicit cache-invalidation hook for the one place a mounted fragment is silently reparented (<component :is="..."> tag switches), with a regression test (test_dynamic_component_tag_switch_updates_correct_owner) that fails without the invalidation and passes with it.

Test plan

  • uv run pytest tests -q — 266 passed, 14 skipped (up from 264/14 baseline; two new regression tests added)
  • uv run ruff check . / uv run ruff format --check . — clean
  • uv run pytest bench --benchmark-only — all 36 benchmark cases pass
  • Verified the _component_parent cache-invalidation regression test fails when the invalidation hook is removed, and passes when restored
  • Confirm the new benchmark.yml workflow runs successfully in CI on this PR

🤖 Generated with Claude Code

Establishes a pytest-benchmark suite (mirroring ../observ's setup) plus a
GitHub Actions workflow that compares PR vs master and fails on >5% mean
regression, so future performance changes have numbers to prove themselves
against. Uses that suite to verify three fixes: weak.py's arity check no
longer builds a full inspect.Signature per callback, ComponentFragment.mount()
reuses the existing first() DFS instead of a hand-rolled BFS, and
Fragment._component_parent() is now cached instead of walking the parent
chain on every attribute update (with an explicit invalidation hook for the
one place a mounted fragment gets silently reparented: dynamic <component
:is="..."> tag switches).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@berendkleinhaneveld

Copy link
Copy Markdown
Collaborator Author

Superseding this with a split series so each change is proven independently by CI:

  1. Benchmark suite + per-PR benchmark workflow (merged first, so subsequent PRs get automatic master-vs-branch comparison)
  2. Mount-path micro-optimizations (weak() arity check, ComponentFragment.mount root lookup, Component.emit)
  3. _component_parent() caching + invalidation
  4. anchor() single scan + loop-invariant anchor hoist in unkeyed v-for updates (new O(n²)→O(n) fix not in this PR)

The sound fixes from this branch are carried over essentially verbatim. Keeping this branch around as reference until the series lands.

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