~bigbes/tarantool

tarantool-protobuf

1004cc60cd4f3912be5360c99c3cf026f41229c0 — Eugene Blikh 3 months ago 6fb4b04
docs: PLAN.md M6 lazy view entry

Marks the M6 "lazy view" bullet done with the API surface, conformance
claim (interop byte-equal through decode_lazy:encode), trace stability
(make jit-trace 19/19), and the honest perf characteristic from
bench/lazy_bench.lua — lazy wins passthrough re-encode (1.0–1.5×),
loses sparse-read 0.60–0.77× on the flat shape because per-segment
table allocation dominates index cost. Mutate-then-reencode is
roughly break-even.

Lazy is a byte-passthrough optimization, not a universal speedup.
1 files changed, 39 insertions(+), 1 deletions(-)

M PLAN.md
M PLAN.md => PLAN.md +39 -1
@@ 173,8 173,46 @@ fiber and bridges client ↔ handler via `fiber.channel`. All four flavors
- [ ] Optional output: ibuf-based encoder that writes into a caller-owned
      `ffi.cdata` byte buffer instead of building a string list. Targets
      hot RPC paths where allocation cost dominates.
- [ ] Decoder fast path that returns an `msgpack.object`-like lazy view
- [x] Decoder fast path that returns an `msgpack.object`-like lazy view
      for nested messages; only materializes touched fields.
      Shipped as `runtime/pb/lazy.lua` + `M.<Type>_decode_lazy` codegen
      stubs in both modes. Surface: `:get / :has / :which / :iter /
      :names` on MessageView; `:len / :at / :iter / :tolist` on
      ArrayView; `:get / :has / :keys / :iter / :totable` on MapView.
      Mutation via `:set` is supported and propagates sub-view edits
      transparently (sub-MessageViews tracked on a flat array for
      JIT-stable `is_dirty` — see lazy.lua's `_sub_msg_views`).
      Re-encode is passthrough: untouched views return their original
      bytes verbatim; partially-dirty views walk fields in id order,
      splicing clean segments and re-emitting dirty ones. WKT
      descriptors (those with `desc.decode`) are eager-wrapped so the
      API stays uniform.

      Conformance: every interop fixture round-trips byte-equal through
      `decode_lazy(b):encode()`. Trace stability: gated by `make
      jit-trace` — index pass, sparse `:get` x2, and passthrough
      `:encode` all compile with no fatal aborts.

      Workload characteristics (from `tarantool bench/lazy_bench.lua`,
      Person at 1KB / 10KB / 100KB):
       - **Passthrough re-encode** is the headline win: **1.0–1.5×**
         faster than eager decode→encode across all sizes and both modes.
         Untouched views never re-walk the wire.
       - **Sparse read** (`:get` two top-level fields) is **0.60–0.77×
         of eager** on the emails-heavy Person shape. Index-build
         allocates one segment table per wire entry, and for messages
         that scale via repeated leaves (no deeply-nested subtrees that
         `skip_field` can vault over), that allocation cost cancels the
         decode-skip savings. Lazy still wins this shape on payloads
         dominated by nested submessages — that case isn't represented
         in the current bench.
       - **Mutate-then-reencode** is **0.81–1.07× of eager**; ~break-even
         to slight loss on the same flat shape. Both decode+set+encode
         and decode_lazy+set+encode walk the full byte range; lazy
         splices substrings, eager re-emits per-field.
      The honest framing: lazy is a *byte-passthrough* optimization;
      it's not a universal speedup. Use it when you decode, touch few
      fields, and re-encode — the common proxy/router shape.

### M7 — Developer ergonomics