From 20efc14f9f8969426d79a5f90e1709955c6e2b29 Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Mon, 15 Jun 2026 17:51:14 +0200 Subject: [PATCH 1/2] Add opt-in fast render context (REACTON_FAST) + benchmark harness The default renderer is unchanged: _RenderContext keeps the original tree-walking (_render/_reconsolidate/_remove_element/_visit_children*), byte-for-byte. A _RenderContextFast(_RenderContext) subclass overrides only those methods with a reimplementation that does work proportional to what changed instead of a full tree walk: - state setters mark needs_render_descendant up the parent chain, so a render pass only descends into subtrees that can contain work; an identical, fully-reconciled, exception-free subtree is skipped in both phases (clean_subtree) and keeps its widgets; - force_update/update/first render walk fully (rc._walk_all); - stale-element removal runs once per context instead of an O(n) set-diff per element; - widget updates are skipped when an identical element reconciles to identical child widgets. Selected with REACTON_FAST=1 (via _render_context_class()); off by default. Side-effect (Layout/Style) widget tracking also moved to ipywidgets' on_widget_constructed hook (shared by both renderers) instead of a per-creation global-dict diff that was O(live widgets). benchmarks/ holds the harness, baseline-vs-fast result JSONs, and README.md documenting the design, the renderer contract both implementations satisfy, the deliberate test-invisible deltas, and known issues found during the work. On a 300-row tree the fast renderer takes a single-leaf update from 13.7ms to 0.62ms (22x) and a memoized-subtree skip from 14.4ms to 1.0ms (14x), behavior-identical to the default. Co-Authored-By: Claude Fable 5 --- benchmarks/README.md | 182 +++++ benchmarks/bench.py | 371 ++++++++++ benchmarks/compare.py | 24 + benchmarks/results/baseline.json | 180 +++++ benchmarks/results/cleanroom.json | 180 +++++ benchmarks/results/reacton-orphan-hook.json | 66 ++ reacton/core.py | 717 +++++++++++++++++++- 7 files changed, 1698 insertions(+), 22 deletions(-) create mode 100644 benchmarks/README.md create mode 100644 benchmarks/bench.py create mode 100644 benchmarks/compare.py create mode 100644 benchmarks/results/baseline.json create mode 100644 benchmarks/results/cleanroom.json create mode 100644 benchmarks/results/reacton-orphan-hook.json diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000..30dc814 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,182 @@ +# Reacton render benchmarks & the opt-in fast reconciler + +This directory holds the performance harness for a faster reimplementation of +the renderer (the tree-walking downstream of `render()`), plus its design notes. + +The fast renderer is **opt-in and off by default.** The default `_RenderContext` +is the original, unchanged implementation; the rewrite lives in a +`_RenderContextFast(_RenderContext)` subclass that overrides only the +tree-walking methods. Set `REACTON_FAST=1` to select it. CI runs the full test +suite against both (a `reacton-fast: ["0", "1"]` matrix dimension), so the two +stay behavior-identical. + +## Running the benchmarks + +```bash +# default renderer, then the fast one: +python benchmarks/bench.py --label baseline +REACTON_FAST=1 python benchmarks/bench.py --label fast +python benchmarks/compare.py baseline fast +``` + +`bench.py` writes `benchmarks/results/