Skip to content

Only propagate newly-dirty vars in _mark_dirty_computed_vars#6743

Open
Alek99 wants to merge 2 commits into
mainfrom
claude/reflex-perf-optimizations-01l7a3-eng-10095
Open

Only propagate newly-dirty vars in _mark_dirty_computed_vars#6743
Alek99 wants to merge 2 commits into
mainfrom
claude/reflex-perf-optimizations-01l7a3-eng-10095

Conversation

@Alek99

@Alek99 Alek99 commented Jul 10, 2026

Copy link
Copy Markdown
Member

Linear: ENG-10095

Description

Every setattr and every proxied append/__setitem__ triggered a full dirty-propagation pass: a needs_update scan over all computed vars (interval expiry) plus a dependency re-walk of the entire accumulated dirty_vars set, not just the newly dirtied var.

  • Each class now precomputes which of its computed vars carry an update interval (_interval_computed_vars, refreshed whenever computed_vars changes), so the expiry scan is skipped entirely for the overwhelmingly common case of none.
  • Each instance tracks a transient "propagated frontier": vars whose dependency closure has already been walked. Subsequent mutations only process newly-dirty vars.

Correctness: re-walking the dirty set is only observably needed after a cached computed var is recomputed mid-cycle (the recompute re-materializes a cache that a later mutation of its dependencies must invalidate again — including across states). Recomputes therefore bump a process-wide generation counter, and a generation mismatch resets the frontier, forcing the same full re-walk as today. The frontier is cleared by _clean and excluded from pickling, so restored/copied states always start with a full walk.

Benchmarks (GitHub Actions runner, run, 2 passes each; state with 2 cached computed vars)

Case main this PR speedup
proxied list append 16.72 / 15.97 us 8.51 / 8.64 us ~1.9x
int var setattr 21.00 / 20.32 us 9.33 / 9.27 us ~2.2x

cProfile (2000 proxied appends): total function calls drop 216k -> 78k; _mark_dirty_computed_vars cumulative time drops 0.102s -> 0.019s and _expired_computed_vars disappears from the profile. The benchmark script also spot-checks that computed vars still recompute correctly after the mutation loop.

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?
    • test_computed_var_recompute_after_mid_cycle_read: mutate -> read (recompute) -> mutate again -> the cache is invalidated again (the exact staleness hazard a naive frontier would introduce).
    • test_computed_var_recompute_after_mid_cycle_read_across_states: same hazard across parent/child states.
    • test_interval_computed_vars_precomputed: per-class interval var precomputation.
  • Have you successfully ran tests with your changes locally?

Note: this touches the same ComputedVar.__get__ region as #6738; whichever merges second needs a trivial rebase.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X


Generated by Claude Code

Every setattr and proxied mutation triggered a full dirty-propagation
pass: an expiry scan over all computed vars plus a dependency re-walk
of the entire accumulated dirty_vars set.

- Precompute per class which computed vars have an update interval, so
  the expiry scan is skipped entirely for the common case of none.
- Track a per-instance "propagated frontier" so each propagation only
  processes vars whose dependency closure has not been walked yet.
  Recomputing a cached var re-materializes its cache, which a later
  mutation of its dependencies must invalidate again, so recomputes
  bump a generation counter that resets the frontier. The frontier is
  transient: cleared by _clean and excluded from pickling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
@Alek99 Alek99 requested a review from a team as a code owner July 10, 2026 20:20
@linear-code

linear-code Bot commented Jul 10, 2026

Copy link
Copy Markdown

ENG-10095

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes computed-var dirty propagation more incremental. The main changes are:

  • Adds a recompute generation counter for cached computed vars.
  • Tracks per-instance dirty propagation frontiers.
  • Precomputes interval-based computed vars per state class.
  • Adds tests for mid-cycle recompute invalidation and interval-var tracking.
  • Adds release notes for the performance improvement.

Confidence Score: 4/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The new frontier state is cleared with dirty state and excluded from serialization.
  • Cached computed-var recomputation updates the generation used to force a fresh dependency walk.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/vars/base.py Adds a module-level recompute generation counter and bumps it when cached sync or async computed vars recompute.
reflex/state.py Adds interval-computed-var precomputation and changes dirty propagation to process only newly dirty frontier vars unless recomputation invalidates the frontier.
tests/units/test_state.py Adds tests for mid-cycle computed-var recomputation, cross-state invalidation, and interval-var precomputation.
news/6743.performance.md Adds a release note for the dirty-propagation performance change.
packages/reflex-base/news/6743.performance.md Adds the package-level release note for the dirty-propagation performance change.

Reviews (1): Last reviewed commit: "Add changelog fragments" | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Jul 10, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 6.93%

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

⚡ 1 improved benchmark
✅ 25 untouched benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
test_process_event 7.6 ms 7.1 ms +6.93%

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-10095 (1c6bebb) 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.

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