Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/6600.performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Speed up reading mutable state vars (lists, dicts, dataclasses) through `MutableProxy`. The per-element check that detects `dataclasses.asdict`/`astuple` recursion now reads `frame.f_code.co_filename` directly instead of calling `inspect.getfile()`, cutting proxy read overhead by roughly 3-4x on large containers without changing behavior.
10 changes: 8 additions & 2 deletions reflex/istate/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
T_STATE = TypeVar("T_STATE", bound="BaseState")
T = TypeVar("T")

# Cached filename of the dataclasses module, used to detect reads originating
# from `dataclasses.asdict`/`astuple` internals on the proxy read hot-path.
_DATACLASSES_FILE = dataclasses.__file__


class StateProxy(wrapt.ObjectProxy):
"""Proxy of a state instance to control mutability of vars for a background task.
Expand Down Expand Up @@ -500,10 +504,12 @@ def _is_called_from_dataclasses_internal() -> bool:
# internal code, for example `asdict` or `astuple`.
frame = inspect.currentframe()
for _ in range(5):
# Why not `inspect.stack()` -- this is much faster!
# Why not `inspect.stack()` -- this is much faster! And reading
# `f_code.co_filename` directly avoids the type-dispatch overhead of
# `inspect.getfile()`, which dominates this per-element read hot-path.
if not (frame := frame and frame.f_back):
break
if inspect.getfile(frame) == dataclasses.__file__:
if frame.f_code.co_filename == _DATACLASSES_FILE:
return True
return False

Expand Down
95 changes: 95 additions & 0 deletions tests/benchmarks/test_state_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Benchmarks for reading state vars through ``MutableProxy``.

Reading a *mutable* var (list/dict/dataclass) returns a ``MutableProxy`` whose
element reads go through ``_wrap_recursive``, which on every element checks
whether the read originates from ``dataclasses`` internals. Reading a
*non-mutable* var (a scalar) returns the value directly with no proxy. These
benchmarks exercise both paths so the per-element proxy read overhead is
measurable.
"""

import dataclasses

import pytest
from pytest_codspeed import BenchmarkFixture

import reflex as rx

N = 10_000


@dataclasses.dataclass
class Point:
"""A simple dataclass element used to exercise recursive proxy wrapping."""

x: int
y: int


class ProxyBenchmarkState(rx.State):
"""State exposing mutable and non-mutable vars for proxy benchmarks."""

scalar: rx.Field[int] = rx.field(0)
numbers: rx.Field[list[int]] = rx.field(default_factory=lambda: list(range(N)))
mapping: rx.Field[dict[int, int]] = rx.field(
default_factory=lambda: dict.fromkeys(range(N), 0)
)
points: rx.Field[list[Point]] = rx.field(
default_factory=lambda: [Point(i, i) for i in range(N)]
)


def _read_scalar(state: ProxyBenchmarkState) -> None:
"""Read a non-mutable var repeatedly (returned directly, no proxy)."""
for _ in range(N):
_ = state.scalar


def _iter_numbers(state: ProxyBenchmarkState) -> None:
"""Iterate a mutable list var (``__iter__`` wraps each element)."""
for _ in state.numbers:
pass


def _index_mapping(state: ProxyBenchmarkState) -> None:
"""Index a mutable dict var (``__getitem__`` wraps each value)."""
mapping = state.mapping
for i in range(N):
_ = mapping[i]


def _iter_points(state: ProxyBenchmarkState) -> None:
"""Iterate a mutable list of dataclasses (recursive proxy wrapping)."""
for _ in state.points:
pass


@pytest.fixture(
params=[
pytest.param(_read_scalar, id="non_mutable_scalar"),
pytest.param(_iter_numbers, id="mutable_list"),
pytest.param(_index_mapping, id="mutable_dict"),
pytest.param(_iter_points, id="mutable_dataclass_list"),
]
)
def access_fn(request: pytest.FixtureRequest):
"""A parametrized var-access routine over mutable and non-mutable vars.

Args:
request: The pytest fixture request carrying the access routine.

Returns:
The access routine to benchmark.
"""
return request.param


def test_var_access(access_fn, benchmark: BenchmarkFixture):
"""Benchmark reading a state var for mutable and non-mutable shapes.

Args:
access_fn: The parametrized var-access routine.
benchmark: The codspeed benchmark fixture.
"""
state = ProxyBenchmarkState() # pyright: ignore [reportCallIssue]
benchmark(lambda: access_fn(state))
Loading