# Performance Plan: pydivkit Casting + Serialization

Context

- Hot paths live in `/Users/p-mosein/arcadia/divkit/public/json-builder/python/pydivkit/core/entities.py`.
- Current optimization improves `_cast_value_type` for common cases (exact type, Optional[None], fewer exceptions).
- Microbench lives in `perf/bench_cast_value_type.py` and includes synthetic `dict()` + `make_div()` benches.


## Goals

- Keep correctness and helpful error messages.
- Reduce overhead from repeated `typing` introspection and per-call allocations.
- Improve end-to-end latency of object construction + `.dict()` + `make_div()` on large trees.
- Avoid relying on mutating `typing` objects (newer Python versions may prevent it).


## Baseline Measurement

Use the microbench to track deltas:

```bash
python3 perf/bench_cast_value_type.py
```

Focus on:

- `union dict dispatch` (currently slightly slower)
- `build+dict` and `build+make_div` (end-to-end)

When changing code, run the bench twice to ensure stability.


## Proposed Improvements

### 1) Cache typing metadata per `type_` (high impact, low risk)

Problem

- `_cast_value_type` calls `get_origin()` and `get_args()` frequently.
- It allocates `set(args_type)` on every Union cast.
- It calls `get_args(type_)` again for the `Expr` fallback.

Plan

- Add a module-level cache keyed by `type_` storing:
  - `origin`
  - `args` (tuple)
  - `args_set` (frozenset)
  - `union_has_none` / `union_has_expr`
  - union dispatch map (`types_by_tag`), if any
- Use `WeakKeyDictionary` when possible to avoid memory leaks; otherwise use a dict with best-effort eviction.

Expected impact

- Removes repeated introspection and eliminates per-call set allocation.
- Likely fixes the small regression in `union dict dispatch`.


### 2) Fast-path non-parameterized runtime types (medium impact, very low risk)

Problem

- Even for simple types, we pay `typing` introspection unless `type(value) == type_`.

Plan

- Before `get_origin/get_args`, add:

  - `if isinstance(type_, type) and isinstance(value, type_): return value`

- Keep the existing exact-type fast-path for maximal speed.

Expected impact

- Faster for typical fields typed as concrete classes (including subclasses).


### 3) Move Union injection bookkeeping off typing objects (correctness + perf)

Problem

- Current implementation mutates typing objects (`setattr(type_, ...)`).
- Some Python versions / typing implementations can reject this.

Plan

- Keep union injection state in module globals:
  - `_UNION_INJECTED: set[Any]`
  - `_UNION_TYPES: dict[Any, Mapping[str, Type[Any]]]`
- Ensure `inject_types(type_)` is called once per union type.

Expected impact

- More robust across Python versions.
- Small speedup by avoiding repeated `getattr/setattr` on typing objects.


### 4) Reduce overhead in `BaseEntity.dict()` for large objects (medium impact)

Problem

- `.dict()` loops over fields and does several dynamic operations per field.
- On large trees, serialization dominates time even if casting is fast.

Plan

- Precompute a per-class “serialization plan” on class creation:
  - A list of `(attr_name, json_key, default, is_ref_capable)`.
- In `.dict()`, iterate the plan and:
  - use `getattr(self, attr_name, default)`
  - write `$key` for refs
  - otherwise `dump(value)`

Notes

- Keep behavior identical (skip `None`, preserve `$field` output).


### 5) Optional: make `dump()` iterative for very deep structures (situational)

Problem

- Deeply nested lists/dicts can hit recursion overhead.

Plan

- Replace recursive `dump()` with an explicit stack for list/dict cases.


## Rollout Strategy

1. Implement metadata caching + remove `set(args_type)` allocation.
2. Re-run `perf/bench_cast_value_type.py`; ensure `union dict dispatch` improves.
3. Add targeted unit tests for Optional/Union edge cases (especially `None`).
4. Consider `.dict()` plan if end-to-end `build+dict` is still dominated by serialization.


## Acceptance Criteria

- No behavior regressions in existing tests.
- Microbench shows:
  - `union dict dispatch` no slower than baseline, ideally faster.
  - `build+dict` and `build+make_div` improved or unchanged.
