Skip deep type-validation on state var hot paths - #6738
Conversation
Greptile SummaryThis PR speeds up hot paths for state validation, computed vars, memoized compilation, and event updates. The main changes are:
Confidence Score: 4/5This is close, but the cached computed-var path should be fixed before merging.
packages/reflex-base/src/reflex_base/vars/base.py Important Files Changed
Reviews (4): Last reviewed commit: "Honor runtime env mode changes in _valid..." | Re-trigger Greptile |
Merging this PR will improve performance by 11.95%
|
| 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
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. ↩
-
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. ↩
| setattr(instance, self._last_updated_attr, datetime.datetime.now()) | ||
| value = getattr(instance, self._cache_attr) | ||
| self._check_deprecated_return_type(instance, value) | ||
| return value |
There was a problem hiding this comment.
if we return early, how does the computed value get cached?
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
f75dcde to
1b480cb
Compare
| return value | ||
|
|
||
| return value | ||
| return getattr(instance, self._cache_attr) |
There was a problem hiding this comment.
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.
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:
1in dev and0in prod (new cached_validation_depth()helper inreflex_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)
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_typeno longer on the cache-hit path) and in prod the whole workload is 538 function calls / 0.003s.Type of change
Changes To Core Features:
tests/units/reflex_base/vars/test_base.py: return type checked on recompute, not on cache hits (sync, async, andcache=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.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