Skip to content

perf(compile): read only set props and cache literal Var dispatch - #7121

Merged
masenf merged 6 commits into
reflex-dev:mainfrom
FarhanAliRaza:farhan/compile-prop-hot-paths
Sep 15, 2026
Merged

masenf merged 6 commits into
reflex-dev:mainfrom
FarhanAliRaza:farhan/compile-prop-hot-paths

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Summary

First of three stacked compile-performance PRs. This one is pure hot-path work with no change to generated output.

  • Component._iter_set_props: _render, _get_vars, and the prop-component scan walked every declared prop (34 to 70 per Radix component) through the field descriptor to find the few that are set. They now iterate the instance dict plus class-level defaults, in declaration order. Literal defaults, @property overrides, and factory defaults still surface.
  • _literal_var_for: caches the literal Var class per exact value type instead of copying and walking the twelve-entry isinstance ladder on every LiteralVar.create. Cleared when a literal subclass registers.
  • insert_app_wraps short-circuits on identity before the field-by-field component comparison.
  • CommonTag now holds the fields and generic render protocol shared by every tag class, with render(children=None) defined as dict(self.set(children=children)). Tag subclasses it and overrides render with a direct fast path that produces the same dictionary (tested). CondTag, MatchTag, IterTag, and Tagless inherit CommonTag, so Component._render and its overrides are annotated -> CommonTag. render_prop passes strings and dicts straight through.
  • cached_property keys use a plain object() instead of uuid4.
  • The memoize plugin's component imports are hoisted to module level (no import cycle exists).

Measurements

Docs site (docs/app, 511 routes), App._compile(dry_run=True) in a fresh process, warm runs:

state compile
main 46.5 to 47.1 s
this PR 39.7 to 40.0 s

cProfile of the same compile: field-descriptor reads fell from 14.9M to 3.1M calls; _render -6.2 s, _get_vars -3.1 s, _create_literal_var -5.1 s, Tag.add_props -2.5 s (profiled time).

Benchmark pages (tests/benchmarks/fixtures.py, full-context compile): complicated page 29.3 ms to 25.0 ms.

Stack

  1. this PR
  2. perf(events): share one chain per handler and trigger across call sites #7122 perf(events): share one chain per handler and trigger across call sites
  3. ENG-10101: perf(memo): evaluate passthrough bodies once and reuse their analysis #7123 perf(memo): evaluate passthrough bodies once and reuse their analysis

Each later PR's diff includes the earlier ones; merge in order.

Test plan

  • New tests: set-prop iteration with defaults, plain-tag render protocol, custom-tag render protocol, render_prop passthrough, literal dispatch invalidation on late registration, cached_property identity, pickle, and release.
  • uv run pytest tests/units/components tests/units/reflex_base tests/units/compiler tests/units/test_event.py green apart from failures that reproduce on clean main in this environment.
  • ruff, pyright, and pre-commit clean.

https://claude.ai/code/session_01PmizE1eQhtYZyVs1RK2ke3

Review in cubic

@FarhanAliRaza
FarhanAliRaza requested a review from a team as a code owner September 11, 2026 23:13
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T23:20:07.816636Z 870787a PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

All reported issues were addressed across 8 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/components/component.py Outdated
Comment thread packages/reflex-base/src/reflex_base/vars/base.py
Comment thread tests/units/reflex_base/vars/test_base.py Outdated
@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no actionable correctness, security, or repository-rule violations remain.

Summary

This PR optimizes several compilation hot paths while preserving the existing render protocol and generated output.

  • Iterates only explicitly set or defaulted component props.
  • Caches literal Var dispatch by exact Python value type and invalidates the cache when literal subclasses register.
  • Introduces CommonTag and gives ordinary Tag rendering a direct fast path.
  • Replaces UUID-based cached-property keys with identity objects and hoists memoization imports.
  • Adds focused regression tests and changelog fragments for every affected package.

Reviews (7) · Last reviewed commit: "Merge branch 'main' into farhan/compile-..."

Comment thread reflex/compiler/plugins/memoize.py

@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: 870787ab22

ℹ️ 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/plugins/memoize.py
Comment thread packages/reflex-base/src/reflex_base/components/component.py
Component render, Var collection, and the prop-component scan walked every
declared prop through the field descriptor to find the few that are set.
Iterate the instance dict plus class-level defaults instead. Cache the
literal Var class per exact value type, short-circuit app-wrap dedupe on
identity, skip the generic tag protocol for plain tags, and hoist the
memoize plugin's component imports.

Docs site dry compile (511 pages): 47 s to 40 s.

Claude-Session: https://claude.ai/code/session_01PmizE1eQhtYZyVs1RK2ke3
@FarhanAliRaza
FarhanAliRaza force-pushed the farhan/compile-prop-hot-paths branch from 870787a to 9300edf Compare September 15, 2026 13:46
@codspeed

codspeed Bot commented Sep 15, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 13.16%

⚡ 9 improved benchmarks
✅ 31 untouched benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
test_compile_page[_complicated_page] 275.8 ms 215.5 ms +28.03%
test_compile_page_full_context[_complicated_page] 321.6 ms 255.9 ms +25.66%
test_compile_all_artifacts[_complicated_page] 154.5 ms 135.8 ms +13.77%
test_compile_page[_stateful_page] 69 ms 61.3 ms +12.56%
test_compile_page_full_context[_stateful_page] 78.4 ms 69.9 ms +12.26%
test_compile_all_artifacts[_stateful_page] 60.6 ms 55.8 ms +8.61%
test_evaluate_page_with_hooks[_complicated_page] 62.9 ms 58.7 ms +7.2%
test_evaluate_page[_complicated_page] 62.4 ms 58.2 ms +7.17%
test_console_log 811.7 µs 770.1 µs +5.41%

Tip

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


Comparing FarhanAliRaza:farhan/compile-prop-hot-paths (f3f0361) with main (2062139)

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.

Comment thread packages/reflex-base/src/reflex_base/vars/base.py
Comment thread packages/reflex-base/src/reflex_base/vars/base.py
Comment thread packages/reflex-base/src/reflex_base/components/tags/cond_tag.py Outdated
Comment thread packages/reflex-base/src/reflex_base/components/tags/tag.py Outdated
Comment thread packages/reflex-base/src/reflex_base/components/tags/tag.py Outdated

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i'm still nervous overall about not having a consistent Tag interface.

I think what we need is to move most Tag functionality into a CommonTag base class that has

def render(self, children: Sequence[Any] | None = None) -> dict[str, Any]:
    if children is not None:
        return dict(self.set(children=children))

Have the specialty tags inherit from this. And have Tag inherit from CommonTag and overwrite the render(...) method with its more efficient implementation.

That way we don't have weird code duplication or ignored params, so CommonTag has a stable interface that is correct top to bottom, but the frequent case can still be performant.

The whole reason Tag and friends exist is to create another layer of indirection between the component and the rendered output. It's mostly an internal implementation detail, but I want to make sure that it remains consistent so we can still reason about it as a concept.

@masenf
masenf merged commit dd849e6 into reflex-dev:main Sep 15, 2026
124 of 125 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.

2 participants