Skip to content

Fuse snapshot memo subtree walks into one traversal#6748

Open
Alek99 wants to merge 2 commits into
mainfrom
claude/reflex-compiler-perf-t8ztc9-15-snapshot-memo-walks
Open

Fuse snapshot memo subtree walks into one traversal#6748
Alek99 wants to merge 2 commits into
mainfrom
claude/reflex-compiler-perf-t8ztc9-15-snapshot-memo-walks

Conversation

@Alek99

@Alek99 Alek99 commented Jul 11, 2026

Copy link
Copy Markdown
Member

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Performance improvement (non-breaking change, identical compile output)

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?

What this does

Compiling a snapshot memo definition (compile_experimental_component_memo, non-passthrough branch) walked the deep-copied subtree four separate times — _get_all_hooks, _get_all_custom_code, _get_all_dynamic_imports, _get_all_imports — each independently recursing children and prop components, and re-calling per-node getters like _get_components_in_props once per artifact. Snapshot memos dominate rx.foreach-heavy pages, so these redundant traversals multiply.

Fix

New _collect_subtree_artifacts gathers all four artifacts in a single traversal. Correctness is order-sensitive (hooks/custom-code dict insertion order determines the emitted JS), so the fused walk reproduces each legacy recursion's exact per-node combination order:

  • hooks: internal → own → added → children, and never from prop subtrees (an in_prop_tree flag mirrors _get_all_hooks skipping prop edges entirely);
  • custom code: own → prop subtrees → own add_custom_code → children (the interleaving _get_all_custom_code uses);
  • dynamic imports: set semantics, order-free;
  • imports: self → children → props, via the same merge_parsed_imports.

The fused walk runs before render() so add_hooks side effects (derived props) land first, matching the legacy hooks-walk-then-render order (the passthrough branch already collects imports before render).

Scoping note

The ticket also covers the tag-hash walks (_get_component_hash). Those are entangled with the known _get_all_hooks_internal shared-cache mutation bug (ENG-10108): today's hash output can depend on cache pollution from prior walks, so a fused hash walk can't be verified byte-identical until that bug is fixed. Deferred to a follow-up after ENG-10108; this PR takes the definition-compile side, which is pollution-free.

Verification

  • New order-sensitive equivalence test: a tree exercising hooks, internal (ref) hooks, per-instance custom code, class-level add_custom_code, dynamic imports, component-typed prop slots, and nested prop-subtree children asserts the fused walk's four results equal the four legacy recursions including dict key order, and that prop-subtree hooks don't leak out.
  • CodSpeed instrumentation on this PR is the authoritative before/after CPU measurement (this sandbox has no PyPI egress for local profiling); numbers will be posted here once the run completes — test_compile_*[_stateful_page] exercises snapshot memos via rx.foreach.

Part of a compiler-performance series; tracked in Linear as ENG-10105.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g


Generated by Claude Code

Compiling a snapshot memo definition walked the (deep-copied) subtree
four times - _get_all_hooks, _get_all_custom_code,
_get_all_dynamic_imports, _get_all_imports - each independently
recursing children and prop components and re-calling per-node getters
like _get_components_in_props.

_collect_subtree_artifacts gathers all four in a single traversal,
combining each artifact at every node in the same order as its
dedicated recursion (hooks stay structural-tree-only; prop-subtree
custom code still lands before the node's own add_custom_code). The
walk runs before render() so add_hooks side effects land first, same
as the legacy hooks-walk-then-render order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
@Alek99 Alek99 requested a review from a team as a code owner July 11, 2026 02:48
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
@greptile-apps

greptile-apps Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR combines snapshot memo artifact collection into one subtree traversal. The main changes are:

  • Adds a fused collector for hooks, custom code, dynamic imports, and imports.
  • Uses the collector while compiling non-passthrough snapshot memos.
  • Adds order-sensitive equivalence tests and a performance changelog.

Confidence Score: 4/5

Artifact collection can run before state needed by custom-code and import getters is established.

  • The old path completed descendant hook side effects before evaluating other artifacts.
  • The old path also rendered the tree before collecting custom code and imports.
  • The new test does not cover getters that depend on either lifecycle step.

reflex/compiler/utils.py and tests/units/compiler/test_compiler_utils.py

Important Files Changed

Filename Overview
reflex/compiler/utils.py Adds the fused traversal, but changes when non-hook artifacts are evaluated relative to descendant hook side effects and rendering.
tests/units/compiler/test_compiler_utils.py Adds detailed ordering tests, but only compares collectors on an unrendered tree with static artifact getters.
news/6748.performance.md Documents the snapshot memo traversal optimization.

Reviews (1): Last reviewed commit: "Add news fragment for snapshot memo walk..." | Re-trigger Greptile

Comment thread reflex/compiler/utils.py
Comment on lines +497 to +499
hooks, custom_code, dynamic_imports, all_imports = _collect_subtree_artifacts(
render
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Artifact Collection Runs Too Early

The legacy path completed the full hooks walk and render() before collecting custom code, dynamic imports, and imports. The fused depth-first call now evaluates a parent’s artifact getters before descendant add_hooks side effects and before rendering, so components that derive artifact requirements from those updates can emit stale custom code or omit required imports.

@codspeed-hq

codspeed-hq Bot commented Jul 11, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/reflex-compiler-perf-t8ztc9-15-snapshot-memo-walks (82b57dd) with main (9a5c4d3)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d47b987d7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread reflex/compiler/utils.py
Comment on lines +433 to +435
child_parts = [
_collect_subtree_artifacts(child, in_prop_tree=in_prop_tree)
for child in component.children

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include VarData component artifacts in fused walk

This fused traversal only descends structural children here (and component props above), but Bare overrides the legacy _get_all_custom_code and _get_all_dynamic_imports to also visit components stored on its contents VarData. After the snapshot memo path replaces the legacy walkers with this helper, a memo body containing a component Var/Bare such as a conditional branch with a NoSSRComponent or a component that returns _get_custom_code() will omit that dynamic import or custom code from the generated memo module, even though the rendered JSX still references it. Please preserve the VarData component edge (or otherwise delegate to the override behavior) when fusing the walk.

Useful? React with 👍 / 👎.

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.

2 participants