Optimize runtime type checks and state delta encoding
FA
1mo ago+865+865+012345678901234567890123456789-67-67-01234567890123456789
Type of change
- New feature (non-breaking change which adds functionality)
- Performance improvement
Description
This PR introduces three performance optimizations for the per-event runtime path:
-
Runtime type validation via pydantic-core (
runtime_isinstance):- Compiles each state var / computed var type hint once into a pydantic-core validator with the same semantics as the one-level
_isinstancecheck (isinstance leaves, strict containers, unions, literals) - Caches compiled validators per type hint
- Falls back to
_isinstance()for hints without an exact schema equivalent (key-level TypedDict checks, non-dict Mappings), for Var values, and when pydantic-core is not installed - Unwraps
MutableProxyvalues, which pydantic-core's container checks would otherwise reject - Replaces
_isinstance()on the hot paths: state var assignments and computed var results
- Compiles each state var / computed var type hint once into a pydantic-core validator with the same semantics as the one-level
-
Compact JSON encoding for state deltas (
json_dumps_compact):- Encodes deltas sent over Socket.IO and streamed upload updates with orjson
- Dataclasses and datetimes keep going through the reflex serializers so output matches
json_dumps; a serializer registered forEnum,UUID, or a subclass switches the wire path back to the stdlib encoder so it is honored - Falls back to the stdlib encoder for integers beyond 64 bits and whenever the payload contains a
null, so the bareNaN/Infinitytokens the frontend revives are preserved orjsonbecomes a dependency ofreflex-base
-
MutableProxy fast paths:
- Resolves the proxy class per (base proxy, wrapped type) through one cached dict lookup
_new_proxy()calls the C allocator and setattr slots directly, skipping the Python-level__new__/__init__dispatch per element- Runs the dataclasses-internal check once per iteration instead of per element
- Returns str/int/float/bool/None values from attribute, item, and iteration reads before touching any wrapping machinery
Measurements
End to end on the reflex-dev/templates dashboard app through its real backend and a Socket.IO client (median round trip, 10k-row items.csv):
| Event | main | this branch |
|---|---|---|
| sort_by_payment | 85.8 ms | 47.9 ms |
| toggle_sort | 134 ms | 83.0 ms |
| next_page | 9.35 ms | 3.45 ms |
| all events, CPU per event | 84.5 ms | 56.1 ms |
New holistic benchmark test_process_table_event (three filter events on a 1000-row table through the real event processor, deltas encoded for the wire): main 26.6 ms, this branch 13.7 ms.
Changes
reflex-base:
utils/types.py:runtime_isinstance()and the schema compilation behind itutils/format.py:json_dumps_compact()utils/serializers.py:overrides_native_json_type()and a bounded per-class dataclass field-name cachevars/base.py: computed var return checks useruntime_isinstance()pyproject.toml: orjson dependency
reflex:
istate/proxy.py: MutableProxy construction, class resolution, scalar reads, iterationstate.py: state var assignment and inline computed var checks useruntime_isinstance()app.py: Socket.IO JSON encoder usesjson_dumps_compact()
reflex-components-core:
core/_upload.py: streamed upload updates usejson_dumps_compact()
Tests:
tests/units/reflex_base/utils/test_types.py: parity ofruntime_isinstance()with_isinstance()across hints and values, Var handling, proxy unwrapping, class-spoofing objects, validator caching and fallbackstests/units/utils/test_format.py:json_dumps_compact()output parity withjson_dumps(), non-finite floats, big integers, custom and overwritten Enum serializerstests/benchmarks/test_event_processing.py:test_process_table_event, one holistic benchmark of the per-event runtime path
News fragments: news/, packages/reflex-base/news/, packages/reflex-components-core/news/