Skip to content

Do not re-set container children that resolve to the same widgets - #61

Merged
maartenbreddels merged 1 commit into
masterfrom
perf/skip-same-container-children
Sep 28, 2026
Merged

maartenbreddels merged 1 commit into
masterfrom
perf/skip-same-container-children

Conversation

@maartenbreddels

@maartenbreddels maartenbreddels commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Why

The fast renderer skips the update of a widget when its element is the same object as in the last render and its kwargs resolve to the same values. But it compared the resolved kwargs (elements replaced by widgets) with the element kwargs (still elements). For a container this compare is always false, so every walked container got its children set again on every update.

For example, one leaf update next to 300 rows re-set the children of the VBox. One root update re-set the children of all 300 HBoxes.

What

For an unchanged element whose kwargs hold elements (a container, or layout=, v_slots=), we compare the resolved kwargs with the values the widget holds now. If they are the same objects, setting them would be a no-op, so we skip it. This change only affects the fast renderer.

A first version compared with the kwargs we set last time, kept in a dict per container. Review found that it changed behavior: a value changed from the frontend was no longer set back. Comparing with the live values keeps the old behavior, and needs no cache that could go stale.

Widget updates with real widgets, 300 rows (fast renderer):

master this PR
leaf update 2 (Button, VBox) 1 (Button)
root update 302 (300 HBox, VBox, Label) 2 (VBox, Label)

Comparing the 302 children with the live value costs ~33 µs. Setting them costs ~670–1900 µs (the timings were measured on a busy machine).

Tests

  • test_leaf_update_does_not_update_sibling_containers: fails on master (fast renderer).
  • test_same_element_sets_back_changes_from_outside: a value or children changed from outside is still set back. It passes on master and failed on the first version of this PR.
  • Guard tests, both renderers: a child widget that changes type, a fragment that grows or shrinks, a fragment whose items are replaced at the same length, and a v_slots child that changes. I broke the compare on purpose (dicts always "same", lists compared by length only), and these tests catch it.
  • Solara's unit suite gives the same results as with 1.10.3, with REACTON_FAST=0 and =1.

From #58 (its second commit), split off so it can ship on its own.

🤖 Generated with Claude Code

The fast renderer skips the update of a widget when its element is the
same object as last render and its kwargs resolve to the same values.
But it compared the resolved kwargs (elements replaced by widgets) with
the element kwargs (still elements), so for every container the compare
failed and its children were assigned again. A leaf update next to 300
rows re-set the children of the VBox (~170 us for 301 real widgets), a
root update re-set the children of all 300 HBoxes.

For an unchanged element whose kwargs hold elements, we now compare the
resolved kwargs with the values the widget holds. When they are the
same objects, setting them is a no-op, so skipping cannot change what
the user sees. A value changed from the frontend is still set back, as
before, and there is no per-widget cache that could go stale.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@maartenbreddels
maartenbreddels force-pushed the perf/skip-same-container-children branch from b5c4ea5 to e565744 Compare September 28, 2026 12:47
@maartenbreddels
maartenbreddels merged commit 970a0a6 into master Sep 28, 2026
24 checks passed
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