Skip to content

Cache Var.to()/guess_type() registry lookups#6742

Open
Alek99 wants to merge 4 commits into
mainfrom
claude/reflex-compiler-perf-t8ztc9-13-var-registry-cache
Open

Cache Var.to()/guess_type() registry lookups#6742
Alek99 wants to merge 4 commits into
mainfrom
claude/reflex-compiler-perf-t8ztc9-13-var-registry-cache

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 dispatch results)

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

Every Var.to() / Var.guess_type() call scanned a reversed copy of the _var_subclasses registry, calling safe_issubclass per entry. In profiling, a single a + b var operation cost ~8.8 µs, of which ~70% was 32 safe_issubclass calls in these scans. This multiplies into every prop of every component during compile.

Fix

The registry is append-only at import time, so type→entry resolution is now three functools.cache'd module-level lookups:

  • _var_subclass_for_conversion(python_type) — the to() python-type dispatch (membership or subclass match),
  • _var_subclass_matching_python_types(types_tuple) — the guess_type() dispatch (1-tuple for plain types, union members otherwise),
  • _var_subclass_for_var_output(output) — the to() Var-subclass dispatch.

Each preserves the reversed-scan priority (later-registered entries win). Var.__init_subclass__ clears the caches on registration, so a Var subclass registered after lookups were cached still takes priority for the types it claims — covered by a new regression test that registers a subclass mid-flight and asserts both to() and guess_type() re-dispatch to it.

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

Overall: +9.68% performance.

Benchmark BASE HEAD Efficiency
test_console_log 423.8 µs 375.3 µs +12.91%
test_evaluate_page[_stateful_page] 5 ms 4.5 ms +12.15%
test_evaluate_page_with_hooks[_stateful_page] 5.8 ms 5.3 ms +10.43%
test_compile_page_full_context[_stateful_page] 35.8 ms 34.6 ms +3.5%

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

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g

Every Var.to()/guess_type() call scanned a reversed copy of
_var_subclasses with safe_issubclass per entry - ~70% of the cost of a
single a + b operation (32 safe_issubclass calls, ~8.8us). The registry
only grows at import time, so the type->entry resolution is now three
functools.cache'd lookups (python-type conversion, guess_type python
types, Var-subclass output), each preserving the reversed-scan priority.
Registering a new Var subclass clears the caches so later entries still
take priority for types they claim.

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 20:09
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR caches Var.to() and Var.guess_type() registry lookups for faster dispatch.

  • Adds cached helper functions for Python-type and Var-subclass lookup.
  • Centralizes Var subclass registration so cache entries are cleared on registry changes.
  • Updates the ReflexURLVar manual registration path to use the cache-clearing helper.
  • Adds a test for dynamic subclass registration after cached lookups.
  • Adds performance news entries.

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/vars/base.py Adds cached registry lookup helpers and routes subclass registration through a helper that clears those caches.
reflex/istate/data.py Updates the ReflexURLVar manual registry entry to use the cache-clearing registration helper.
tests/units/vars/test_base.py Adds a test that confirms later subclass registration updates cached dispatch results.
news/6742.performance.md Adds a news entry for cached Var registry lookups.
packages/reflex-base/news/6742.performance.md Adds the reflex-base news entry for cached Var registry lookups.

Reviews (3): Last reviewed commit: "Add root news fragment for Var registry ..." | Re-trigger Greptile

Comment thread packages/reflex-base/src/reflex_base/vars/base.py
@linear-code

linear-code Bot commented Jul 10, 2026

Copy link
Copy Markdown

ENG-10103

@codspeed-hq

codspeed-hq Bot commented Jul 10, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 9.54%

⚠️ 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

⚡ 4 improved benchmarks
✅ 22 untouched benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
test_console_log 423.8 µs 374.6 µs +13.14%
test_evaluate_page[_stateful_page] 5 ms 4.5 ms +11.8%
test_evaluate_page_with_hooks[_stateful_page] 5.8 ms 5.3 ms +10.05%
test_compile_page_full_context[_stateful_page] 35.8 ms 34.6 ms +3.42%

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-13-var-registry-cache (af2b18c) 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: e884352a72

ℹ️ 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".

fixed_output_type, var_subclass.python_types
):
return self.to(var_subclass.var_subclass, output)
conversion_entry = _var_subclass_for_conversion(fixed_output_type)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Guard cached lookups against unhashable types

When output is a real class whose metaclass defines equality but no hash, this call now raises TypeError before reaching the existing object-var fallback or _var_type replacement, because _var_subclass_for_conversion is wrapped in functools.cache and hashes fixed_output_type. The previous reversed scan did not require type objects to be hashable, and the same cached-key regression can also hit guess_type() via _var_subclass_matching_python_types; please fall back to the uncached scan or otherwise handle unhashable type keys.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not a regression, so no fallback added: every one of these call sites is preceded by get_origin(...) on the same key (fixed_output_type = get_origin(output) or output, and guess_type normalizes via value_inside_optional/get_origin first). types.get_origin falls through to _get_origin_cached, which is @lru_cache-wrapped — so a class whose metaclass defines __eq__ without __hash__ already raised TypeError there on main, before ever reaching the registry scan. The new caches don't change the set of types that can pass through to()/guess_type().


Generated by Claude Code

claude added 2 commits July 11, 2026 02:17
The manual ReflexURLVar registry append bypassed the lookup-cache clear
added for __init_subclass__ registrations, so a lookup cached before
reflex.istate.data imports could keep resolving ReflexURL to a stale
entry. All appends now go through _register_var_subclass_entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
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