Cache Var.to()/guess_type() registry lookups#6742
Conversation
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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
Greptile SummaryThis PR caches
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (3): Last reviewed commit: "Add root news fragment for Var registry ..." | Re-trigger Greptile |
Merging this PR will improve performance by 9.54%
|
| 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)
Footnotes
-
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. ↩
There was a problem hiding this comment.
💡 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) |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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
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
All Submissions:
Type of change
Changes To Core Features:
What this does
Every
Var.to()/Var.guess_type()call scanned a reversed copy of the_var_subclassesregistry, callingsafe_issubclassper entry. In profiling, a singlea + bvar operation cost ~8.8 µs, of which ~70% was 32safe_issubclasscalls 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)— theto()python-type dispatch (membership or subclass match),_var_subclass_matching_python_types(types_tuple)— theguess_type()dispatch (1-tuple for plain types, union members otherwise),_var_subclass_for_var_output(output)— theto()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 bothto()andguess_type()re-dispatch to it.Measured results (CodSpeed instrumentation, this PR vs
main@ 9a5c4d3)Overall: +9.68% performance.
BASEHEADtest_console_logtest_evaluate_page[_stateful_page]test_evaluate_page_with_hooks[_stateful_page]test_compile_page_full_context[_stateful_page]Part of a compiler-performance series; tracked in Linear as ENG-10103.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g