Optimize runtime type checks and state delta encoding

FA
1mo ago
+865-67

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:

  1. 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 _isinstance check (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 MutableProxy values, which pydantic-core's container checks would otherwise reject
    • Replaces _isinstance() on the hot paths: state var assignments and computed var results
  2. 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 for Enum, 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 bare NaN/Infinity tokens the frontend revives are preserved
    • orjson becomes a dependency of reflex-base
  3. 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):

Eventmainthis branch
sort_by_payment85.8 ms47.9 ms
toggle_sort134 ms83.0 ms
next_page9.35 ms3.45 ms
all events, CPU per event84.5 ms56.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 it
  • utils/format.py: json_dumps_compact()
  • utils/serializers.py: overrides_native_json_type() and a bounded per-class dataclass field-name cache
  • vars/base.py: computed var return checks use runtime_isinstance()
  • pyproject.toml: orjson dependency

reflex:

  • istate/proxy.py: MutableProxy construction, class resolution, scalar reads, iteration
  • state.py: state var assignment and inline computed var checks use runtime_isinstance()
  • app.py: Socket.IO JSON encoder uses json_dumps_compact()

reflex-components-core:

  • core/_upload.py: streamed upload updates use json_dumps_compact()

Tests:

  • tests/units/reflex_base/utils/test_types.py: parity of runtime_isinstance() with _isinstance() across hints and values, Var handling, proxy unwrapping, class-spoofing objects, validator caching and fallbacks
  • tests/units/utils/test_format.py: json_dumps_compact() output parity with json_dumps(), non-finite floats, big integers, custom and overwritten Enum serializers
  • tests/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/