Skip to content

Dedup auto-memoize passthrough definitions during compile#6735

Open
Alek99 wants to merge 5 commits into
mainfrom
claude/reflex-compiler-perf-t8ztc9-11-memoize-dedup
Open

Dedup auto-memoize passthrough definitions during compile#6735
Alek99 wants to merge 5 commits into
mainfrom
claude/reflex-compiler-perf-t8ztc9-11-memoize-dedup

Conversation

@Alek99

@Alek99 Alek99 commented Jul 10, 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

The auto-memoize plugin (MemoizeStatefulPlugin) built a full memo definition for every stateful call site and only deduped by (tag, source_module) afterwards. In profiling of a 500-duplicated-row page this was ~56% of compile CPU: 1,001 definitions were built and then collapsed to 3 unique ones. Per call site it:

  1. ran _analyze_params twice on a constant signature — with from __future__ import annotations, get_type_hints eval-compiles the stringized annotations each time (2,472 builtins.compile calls in the profile), and
  2. evaluated the memo body twice (once for the tag preview, once inside _create_component_definition).

Fix (behavior-preserving)

  • Hoist the params analysis to an import-time constant (_PASSTHROUGH_PARAMS): the passthrough wrapper signature is always (children: Var[Component]) -> Component.
  • Reuse the evaluated preview as the definition body (_LazyBody.ready(_lift_rest_props(preview))) instead of re-running the memo function.
  • Check CompileContext.auto_memo_components for the computed (tag, source_module) key first (new optional existing_definitions parameter): repeated identical subtrees reuse the first definition instead of rebuilding an equivalent one per call site. The cache stays per-CompileContext, so definitions are still never shared across compiles (covered by test_passthrough_memo_definitions_are_not_shared_globally).

Compile output is unchanged: the surviving definition is structurally identical to the one that previously overwrote the registry entry last.

Measured results (CodSpeed instrumentation, this PR vs main @ 9a5c4d3)

Overall: +21.96% compile performance.

Benchmark BASE HEAD Efficiency
test_compile_all_artifacts[_stateful_page] 27.2 ms 21.7 ms +25.74%
test_compile_page[_stateful_page] 30.8 ms 25.3 ms +21.94%
test_compile_page_full_context[_stateful_page] 35.8 ms 30.3 ms +18.3%
test_compile_page_duplicated_rows (new) N/A 828.1 ms baseline for follow-ups

Verification

  • New unit tests: cache-hit returns the cached definition object (and a different source_module still misses); the memo body is evaluated exactly once per definition build.
  • New CodSpeed benchmark test_compile_page_duplicated_rows (200 identical stateful rows) stresses this path so follow-up compiler-perf PRs are measured on every run.

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

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g

The auto-memoize plugin built a full memo definition for every stateful
call site and only deduped by tag afterwards. Per component this ran
_analyze_params twice on a constant signature (get_type_hints
eval-compiles the stringized annotations each time) and evaluated the
memo body twice.

- Hoist the passthrough params analysis to an import-time constant; the
  wrapper signature (children: Var[Component]) never changes.
- Reuse the evaluated preview as the definition body instead of
  re-running the memo function inside _create_component_definition.
- Check CompileContext.auto_memo_components for the computed (tag,
  source_module) key before building, so repeated identical subtrees
  reuse the first definition instead of rebuilding an equivalent one
  per call site.

Adds a duplicated-rows compile benchmark that stresses this path.

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 10, 2026 19:31
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR speeds up auto-memoized passthrough component compilation. The main changes are:

  • Reuses memo definitions within a compile context.
  • Computes passthrough tags before building memo bodies.
  • Caches shared passthrough parameter and hole rendering work.
  • Extends memo hashes to include dynamic imports.
  • Adds unit coverage and a duplicated-rows benchmark.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/components/memo.py Adds passthrough memo tag precomputation, per-compile definition reuse, and shared passthrough helper caching.
reflex/compiler/plugins/memoize.py Uses the memo definition registry during stateful auto-memoization and only rewrites event triggers when a body is built.
reflex/compiler/utils.py Updates passthrough memo compilation to render the stored hole-based memo body directly.
packages/reflex-base/src/reflex_base/components/component.py Optimizes deterministic component hashing and includes dynamic imports in memo tag inputs.
tests/units/compiler/test_memoize_plugin.py Adds tests for memo definition reuse, module separation, trigger rewriting, and dynamic import tag coverage.
tests/benchmarks/test_compilation.py Adds a benchmark for repeated stateful rows that exercise memo definition deduplication.

Reviews (5): Last reviewed commit: "perf(memoize): compute memo tag up front..." | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Jul 10, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 29.41%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 3 improved benchmarks
✅ 23 untouched benchmarks
🆕 1 new benchmark
⏩ 8 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
test_compile_page[_stateful_page] 30.8 ms 23.1 ms +33.2%
test_compile_all_artifacts[_stateful_page] 27.2 ms 21.3 ms +27.79%
test_compile_page_full_context[_stateful_page] 35.8 ms 28.1 ms +27.31%
🆕 test_compile_page_duplicated_rows N/A 646.9 ms N/A

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/reflex-compiler-perf-t8ztc9-11-memoize-dedup (0390d78) 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.

@linear-code

linear-code Bot commented Jul 10, 2026

Copy link
Copy Markdown

ENG-10101

claude added 3 commits July 10, 2026 20:01
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
create_passthrough_component_memo gained the existing_definitions
parameter, changing the generated stub.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
…build

Hash the call-site component with a constant {children}-hole render to
derive the memo tag before any body work happens. On a registry hit the
plugin now skips the event-trigger rewrite and the memo body build
entirely; on a miss the new prepare_body hook applies the rewrite and
the definition self-registers.

Soundness of dedup-by-tag requires the component hash to cover every
input the memo module emission reads, so dynamic imports are now hashed
(with a regression test). The hash walk also gets exact-type fast paths
for str/dict/list and a per-class dataclass field-name cache.
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.

3 participants