Commit 2e48305
Avoid redundant anchor lookups in Fragment.anchor() and unkeyed v-for (#188)
* Add pytest-benchmark suite and per-PR benchmark CI workflow
Adds a bench/ suite (element creation, component mount depth,
attribute update at depth, keyed/unkeyed v-for reconciliation, and
unkeyed list grow/shrink) using pytest-benchmark with DictRenderer and
synchronous event loop, mirroring the setup in the sibling observ repo.
The new benchmark workflow runs the suite twice on every PR - once
against the master version of collagraph/ and once against the PR
version - and fails when mean time regresses more than 5%. The CI test
job is scoped to the tests directory so the matrix does not execute
benchmarks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Disable GC during timed list-benchmark calls to reduce variance
The allocation burst of list reconciliation triggers collection pauses
in some rounds, which inflated stddev on the grow benchmarks to ~40%
of the mean - far too noisy for the 5% regression gate in CI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Speed up mount path: cheap arity check, reuse first(), leaner emit
- weak(): read __code__.co_argcount instead of building a full
inspect.Signature for every wrapped watcher callback; this runs for
every dynamic bind during create/mount.
- ComponentFragment.mount(): use the existing first() helper to resolve
the component root element instead of a hand-rolled BFS with a
per-mount deque import. Adds a regression test for a component whose
root element sits behind a v-if wrapper.
- Component.emit(): avoid creating defaultdict entries for events that
have no handlers and skip the set copy when empty.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Cache Fragment._component_parent() lookups
The parent-chain walk ran on every reactive attribute update (via
_set_attr/_rem_attr calling component.updated()) and on every ref
(un)registration, making update cost scale with fragment depth.
The result is now cached per fragment. The cache is invalidated in the
parent property setter, on unmount(destroy=True), and - via a new
recursive helper - after DynamicFragment._create_fragment_for_tag()
transfers live children to a new active fragment on a <component :is>
tag switch, which reassigns _parent directly and changes the owning
component of every transferred descendant.
Two regression tests cover the tag-switch reparenting: updated()
attribution and string-ref registration must follow the new owner.
Both fail when the invalidation hook is removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Avoid redundant anchor lookups in Fragment.anchor() and unkeyed v-for
- anchor(): a membership check followed by .index() scanned the
parent's children twice; use a single .index() in a try/except,
for both children and slot_contents.
- Unkeyed ListFragment updates mounted every appended fragment with a
freshly computed self.anchor(), which walks the tree per item even
though the anchor (the first element after the list) cannot change
while appending items from the list's own subtree. Compute it lazily
once per update run, turning growth by n items from O(n^2) anchor
work into O(n). Same hoist in ListFragment.mount().
Adds a regression test asserting that growing an unkeyed v-for with a
trailing sibling inserts elements in order, each anchored before that
sibling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent 2b6a634 commit 2e48305
2 files changed
Lines changed: 76 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
| 148 | + | |
149 | 149 | | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
150 | 154 | | |
151 | 155 | | |
152 | 156 | | |
153 | 157 | | |
154 | 158 | | |
155 | 159 | | |
156 | 160 | | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
164 | 173 | | |
165 | 174 | | |
166 | 175 | | |
| |||
706 | 715 | | |
707 | 716 | | |
708 | 717 | | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
709 | 723 | | |
710 | 724 | | |
711 | 725 | | |
712 | 726 | | |
713 | 727 | | |
| 728 | + | |
| 729 | + | |
714 | 730 | | |
715 | 731 | | |
716 | 732 | | |
| |||
719 | 735 | | |
720 | 736 | | |
721 | 737 | | |
722 | | - | |
| 738 | + | |
723 | 739 | | |
724 | 740 | | |
725 | 741 | | |
726 | 742 | | |
727 | 743 | | |
| 744 | + | |
728 | 745 | | |
729 | 746 | | |
730 | | - | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
731 | 750 | | |
732 | 751 | | |
733 | 752 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
675 | 675 | | |
676 | 676 | | |
677 | 677 | | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
0 commit comments