Skip to content

v0.25.0

Choose a tag to compare

@rustyconover rustyconover released this 17 Jul 01:30
· 284 commits to main since this release

perf(utils): cache the per-class serialization plan on the message hot path

ArrowSerializableDataclass._to_row_dict and deserialize_from_batch ran get_type_hints(include_extras=True) and re-derived the transient / required-field / ArrowType(pa.binary()) facts on every RPC message — profiling a downstream integration suite (the VGI DuckDB extension) showed typing introspection (~22%) and isinstance dispatch chains (~33%, including the runtime-checkable _BytesSerializable Protocol check) dominating the Python-side cost per message.

  • _serialization_plan(cls) resolves type hints once per class and caches a _SerializationPlan (per-field resolved/unwrapped annotation, transient flag, binary-override flag, defaults, required-field list) on the class itself — same cls.__dict__ idiom as _ArrowSchemaDescriptor, so subclasses never inherit a parent's cached plan.
  • Serialize and deserialize consume the plan instead of re-resolving annotations per call.
  • _convert_value_for_serialization gains an exact-type fast path for plain str/int/float/bool/bytes values (exact checks, so Enum members — str/int subclasses — still take the full dispatch).

Measured (8-field message, Python 3.13): _serialize 65 → 26 µs (−60%), serialize_to_bytes 78 → 38 µs (−51%), deserialize_from_batch 48 → 27 µs (−43%).

No wire-format or API changes. Verified with the full test suite (3659 passed), strict mypy/ty/ruff/pydoclint, and the VGI DuckDB extension's 285-test integration suite run against this build.