Skip to content

Skip deep type-validation on state var hot paths - #6738

Open
Alek99 wants to merge 3 commits into
claude/reflex-compiler-perf-t8ztc9-11-memoize-dedupfrom
claude/reflex-perf-optimizations-01l7a3-eng-10093
Open

Skip deep type-validation on state var hot paths#6738
Alek99 wants to merge 3 commits into
claude/reflex-compiler-perf-t8ztc9-11-memoize-dedupfrom
claude/reflex-perf-optimizations-01l7a3-eng-10093

Conversation

@Alek99

@Alek99 Alek99 commented Jul 10, 2026

Copy link
Copy Markdown
Member

Linear: ENG-10093

Description

Two hot paths ran _isinstance(value, type, nested=1), which walks every element of list/dict values, only to gate a diagnostic log that never changes behavior:

  • BaseState.__setattr__ validated the full container on every assignment.
  • ComputedVar.__get__ / AsyncComputedVar.__get__ validated the return type on every access, including cache hits.

Changes:

  • Computed var return types are now validated only when the value is actually recomputed (sync and async). Cache hits skip the check entirely.
  • Element-wise validation depth is now 1 in dev and 0 in prod (new cached _validation_depth() helper in reflex_base.utils.types), so production only checks the outer type. Dev keeps the exact same diagnostics as before.

Benchmarks (GitHub Actions runner, run, 2 passes each)

Case main this PR speedup
read cached computed var returning 10k dicts (dev) 13.81 / 14.05 ms 0.0015 / 0.0015 ms ~9,200x
read cached computed var (prod) 13.66 / 13.93 ms 0.0013 / 0.0015 ms ~9,300x
assign 100k-int list (prod) 90.89 / 90.96 ms 0.020 / 0.024 ms ~4,000x
assign 100k-int list (dev) 90.74 / 92.67 ms 91.76 / 94.74 ms unchanged (by design)

cProfile (3 assigns + 20 cached reads, dev): main spends 3.06s in 10.5M calls dominated by _isinstance (510k calls); with this PR the cached-read side disappears (_check_deprecated_return_type no longer on the cache-hit path) and in prod the whole workload is 538 function calls / 0.003s.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

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?
    • tests/units/reflex_base/vars/test_base.py: return type checked on recompute, not on cache hits (sync, async, and cache=False).
    • tests/units/reflex_base/utils/test_types.py: _validation_depth() is 0 in prod / 1 in dev.
    • tests/units/test_state.py: wrong-typed assignment still logs an error in dev.
  • Have you successfully ran tests with your changes locally?

Note: behavior-wise, a wrong-typed computed var now logs once per recompute instead of once per access, and prod no longer walks container elements for the log-only check. No exceptions or control flow depend on these checks.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X


Generated by Claude Code

@Alek99
Alek99 requested a review from a team as a code owner July 10, 2026 19:53
@linear-code

linear-code Bot commented Jul 10, 2026

Copy link
Copy Markdown

ENG-10093

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR speeds up hot paths for state validation, computed vars, memoized compilation, and event updates. The main changes are:

  • Shallow production validation for state assignment and computed-var return diagnostics.
  • Computed-var type checks moved off cached reads and onto recompute paths.
  • Passthrough memo definitions deduplicated during compilation.
  • Deterministic component hashing optimized for common value shapes.
  • Websocket update emission simplified to avoid an extra task wrapper.

Confidence Score: 4/5

This is close, but the cached computed-var path should be fixed before merging.

  • The environment-depth helper now updates correctly when the mode changes.
  • Cached computed vars can still keep prod-validated values silent after switching to dev.
  • The issue is limited to diagnostics, but it breaks the intended dev-mode behavior.

packages/reflex-base/src/reflex_base/vars/base.py

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/utils/types.py Adds environment-aware validation depth that re-reads REFLEX_ENV_MODE and caches only the raw mode-to-depth mapping.
reflex/state.py State assignment validation now uses the shared validation-depth helper.
packages/reflex-base/src/reflex_base/vars/base.py Computed-var validation now runs on recompute, but cached reads can miss dev diagnostics after a mode switch.
packages/reflex-base/src/reflex_base/components/memo.py Passthrough memo creation now computes tags up front and reuses registered definitions.
packages/reflex-base/src/reflex_base/components/component.py Component hashing now handles common exact types earlier and includes dynamic imports in memo hash coverage.
reflex/app.py Update emission now awaits the socket emit directly and yields one event-loop tick afterward.

Reviews (4): Last reviewed commit: "Honor runtime env mode changes in _valid..." | Re-trigger Greptile

Comment thread packages/reflex-base/src/reflex_base/utils/types.py
@codspeed-hq

codspeed-hq Bot commented Jul 10, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 11.95%

⚠️ 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
✅ 24 untouched benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
test_console_log 423.2 µs 373.9 µs +13.21%
test_evaluate_page[_stateful_page] 5.1 ms 4.5 ms +12.33%
test_evaluate_page_with_hooks[_stateful_page] 5.9 ms 5.3 ms +10.33%

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-perf-optimizations-01l7a3-eng-10093 (1b480cb) with claude/reflex-compiler-perf-t8ztc9-11-memoize-dedup (0390d78)2

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.

  2. No successful run was found on claude/reflex-compiler-perf-t8ztc9-11-memoize-dedup (dea5c0c) during the generation of this report, so 8333924 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Comment thread packages/reflex-base/src/reflex_base/utils/types.py
setattr(instance, self._last_updated_attr, datetime.datetime.now())
value = getattr(instance, self._cache_attr)
self._check_deprecated_return_type(instance, value)
return value

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.

if we return early, how does the computed value get cached?

claude added 3 commits July 17, 2026 18:44
Assigning a state var and reading a computed var both ran
_isinstance(value, type, nested=1), walking every element of
list/dict values only to gate a diagnostic log. The computed var
check also ran on every access, including cache hits.

- Validate computed var return types only when the value is
  recomputed (sync and async), not on cache hits.
- Validate one container level deep only in dev mode; production
  now checks just the outer type (the check never gates behavior,
  it only logs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
Address review feedback: instead of caching the first observed mode
forever, re-read the raw REFLEX_ENV_MODE value on every call and cache
the depth per raw value, so in-process mode changes take effect
immediately at negligible hot-path cost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
@Alek99
Alek99 force-pushed the claude/reflex-perf-optimizations-01l7a3-eng-10093 branch from f75dcde to 1b480cb Compare July 18, 2026 01:46
@Alek99
Alek99 changed the base branch from main to claude/reflex-compiler-perf-t8ztc9-11-memoize-dedup July 18, 2026 01:46
return value

return value
return getattr(instance, self._cache_attr)

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 Cache Hit Skips Dev Validation This cache-hit path can still suppress dev diagnostics after an in-process mode switch. A computed var can be cached while REFLEX_ENV_MODE=prod, where _validation_depth() only checks the outer container, then the same process can switch to dev with environment.REFLEX_ENV_MODE.set(...). If the computed var has no dependency change, this line returns the cached value without calling _check_deprecated_return_type, so nested wrong types stay silent until an unrelated recompute happens. Please make cached values account for validation-depth changes, such as by rechecking or invalidating the cache when the mode changes.

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