Skip to content

Optimize MutableProxy dataclasses detection on hot-path#6600

Open
masenf wants to merge 2 commits into
mainfrom
claude/gracious-johnson-6FlzO
Open

Optimize MutableProxy dataclasses detection on hot-path#6600
masenf wants to merge 2 commits into
mainfrom
claude/gracious-johnson-6FlzO

Conversation

@masenf
Copy link
Copy Markdown
Collaborator

@masenf masenf commented Jun 3, 2026

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?

Description

This PR optimizes the performance of state var reads through MutableProxy by improving the _is_called_from_dataclasses_internal() check, which runs on every element access in the hot-path.

Changes:

  1. Cache dataclasses module filename (reflex/istate/proxy.py):

    • Store dataclasses.__file__ in a module-level constant _DATACLASSES_FILE to avoid repeated attribute lookups
    • Replace inspect.getfile(frame) with direct frame.f_code.co_filename access, avoiding the type-dispatch overhead of inspect.getfile() which dominates this per-element read hot-path
    • Updated comments to explain the performance rationale
  2. Add comprehensive benchmarks (tests/benchmarks/test_state_proxy.py):

    • New benchmark suite measuring proxy read overhead across different var types:
      • Non-mutable scalar reads (baseline, no proxy)
      • Mutable list iteration (proxy wrapping each element)
      • Mutable dict indexing (proxy wrapping each value)
      • Mutable list of dataclasses (recursive proxy wrapping with dataclasses detection)
    • Parametrized fixture for easy comparison of access patterns
    • Exercises both the direct-return path (scalars) and the proxy-wrapping path (mutable types)

The optimization targets the per-element proxy read hot-path where _is_called_from_dataclasses_internal() is called repeatedly during iteration or indexing of mutable vars. By eliminating the expensive inspect.getfile() call and caching the filename, we reduce overhead on every element access.

Test Plan

  • Added tests/benchmarks/test_state_proxy.py with parametrized benchmarks covering scalar, list, dict, and dataclass list access patterns
  • Benchmarks can be run with uv run pytest tests/benchmarks/test_state_proxy.py -v to measure the optimization impact
  • Existing unit tests continue to pass, ensuring no behavioral changes

https://claude.ai/code/session_01MWnJui2xhcgH5GsXaJqta5

MutableProxy._wrap_recursive runs _is_called_from_dataclasses_internal on
every element read (__getitem__/__iter__), which walked up to 5 frames
calling inspect.getfile() per frame. For a frame, inspect.getfile() just
returns frame.f_code.co_filename after a chain of is*() type checks; those
checks are the overhead and dominate the per-element read cost.

Read frame.f_code.co_filename directly (equivalent for frames) and cache
dataclasses.__file__ in a module constant. asdict/astuple behavior is
unchanged. Iterating/indexing large mutable vars is ~3-4x faster.

Add tests/benchmarks/test_state_proxy.py covering var reads across mutable
(list/dict/dataclass) and non-mutable (scalar) parametrizations.
@masenf masenf requested a review from a team as a code owner June 3, 2026 21:25
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jun 3, 2026

Merging this PR will not alter performance

βœ… 24 untouched benchmarks
πŸ†• 4 new benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
πŸ†• test_var_access[mutable_dataclass_list] N/A 206.8 ms N/A
πŸ†• test_var_access[mutable_dict] N/A 88 ms N/A
πŸ†• test_var_access[mutable_list] N/A 70.9 ms N/A
πŸ†• test_var_access[non_mutable_scalar] N/A 57.4 ms N/A

Comparing claude/gracious-johnson-6FlzO (e7d9eb2) with main (bb8b6f5)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 3, 2026

Greptile Summary

This PR optimizes the _is_called_from_dataclasses_internal() hot-path check in MutableProxy by caching dataclasses.__file__ at import time and reading frame.f_code.co_filename directly instead of calling inspect.getfile(), which carries type-dispatch overhead on every proxy element access.

  • reflex/istate/proxy.py: Adds _DATACLASSES_FILE module-level constant and swaps inspect.getfile(frame) == dataclasses.__file__ for frame.f_code.co_filename == _DATACLASSES_FILE, reducing per-element overhead during list/dict iteration through a proxy.
  • tests/benchmarks/test_state_proxy.py: Adds a pytest-codspeed benchmark suite (scalar, list, dict, and dataclass-list access patterns) matching the conventions of the existing benchmark files in the project.

Confidence Score: 5/5

Safe to merge; the optimization is a drop-in replacement that behaves identically to the previous code in all realistic CPython deployments.

The change swaps inspect.getfile(frame) for the direct frame.f_code.co_filename attribute read. In CPython, inspect.getfile() for a frame ultimately resolves to co_filename after type-dispatch and a getsourcefile roundtrip, so the comparison result is identical in any environment where dataclasses.__file__ is already the source .py path (the normal case). In .pyc-only environments both old and new code fail the comparison the same way β€” no regression is introduced. The benchmark additions follow the exact import and fixture pattern already used in tests/benchmarks/test_compilation.py and rely on pytest-codspeed, which is a declared test dependency.

No files require special attention.

Important Files Changed

Filename Overview
reflex/istate/proxy.py Caches dataclasses.__file__ into _DATACLASSES_FILE and replaces inspect.getfile(frame) with frame.f_code.co_filename in the hot-path check; semantically equivalent in all common CPython environments.
tests/benchmarks/test_state_proxy.py New benchmark suite following the existing project pattern; imports pytest-codspeed (already a listed dev dependency), correctly exercises both the direct-return scalar path and the MutableProxy per-element wrap path.

Reviews (1): Last reviewed commit: "perf(proxy): avoid inspect.getfile on Mu..." | Re-trigger Greptile

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