bench: per-helper wire bench + shape-variety bench + multi-byte jit gates
Three additions so future perf regressions are visible at the right
granularity, not just averaged out by the single Person shape in
bench/bench.lua.
bench/wire_bench.lua (new). Microbenches every wire helper in
isolation — encode/decode for varint at 1/2/3/5 bytes, zigzag, fixed
widths, float/double, LEN, tag, skip_field, utf8 validator. Used when
tuning wire.lua to confirm a change moved the helper-level ns/op as
expected (e.g. encode_varint(150) 529 ns -> 68 ns from the 2-byte
fast path). Run via `make bench-wire`. No baseline, no regression
gate — this is a manual inspection tool.
bench/shapes_bench.lua (new). Runs encode + decode against a handful
of fundamentally different Person / Event / Result shapes —
scalar-heavy, packed-int (100 and 1000 elements), nested-friends
(10 and 100), maps (scalar-valued and message-valued), oneof, and
WKT-heavy. Each shape dials one knob up so cost attribution stays
clean. Shows alloc B/op alongside throughput. Run via
`make bench-shapes`. Caught the packed-int / multi-byte-varint
opportunity that bench/bench.lua (all 1-byte varints, fixed
shape) doesn't surface.
bench/jit_trace.lua + 4 gates. Two new fixtures per mode that
specifically exercise encode_varint_slow's 2/3-byte Lua-number paths
and decode_string's multi-byte LEN fallback (200-byte name + packed
ints in [150..500000]). If a future change pushes encode_varint_slow
past LuaJIT's inline budget the gate fires instead of the regression
landing silently in shapes_bench. Total gate count: 19 -> 23.
Makefile gains `bench-wire` and `bench-shapes` phony targets.
bench: lazy decode/encode scenarios + jit-trace coverage
Adds bench/lazy_bench.lua comparing eager decode/encode against
decode_lazy:encode across three workloads (passthrough, sparse-read,
mutate-then-reencode) at 1KB/10KB/100KB on emails-heavy Person.
Output is stderr-only (varies with CPU load; not committed to
baseline.json).
Findings (LuaJIT 2.1.0-beta3, both modes):
- Passthrough re-encode: lazy 1.0–1.5× faster. Untouched views skip
field-walk entirely and return their original bytes verbatim.
- Sparse :get x2: lazy 0.60–0.77× of eager. Per-segment Lua tables
allocated during the index pass cancel the decode-skip savings on
this flat shape (no large subtrees to skip_field over).
- Mutate-then-reencode: lazy 0.81–1.07× of eager — roughly
break-even. Both paths traverse the full byte range; lazy
splices, eager re-emits per field.
Lazy is a byte-passthrough optimization, not a universal speedup. Use
it when you decode, touch few fields, and re-encode — the proxy /
router shape.
Also extends bench/jit_trace.lua with three lazy hot-path checks:
index pass, sparse :get x2, and untouched :encode. All compile with
no fatal aborts (19/19 trace-stability checks pass; one harmless
side-trace bridge per scenario at lazy.lua's index loop, same pattern
as the existing decode_varint bridge).
M6: trace stability gate + two fixes
Add `make jit-trace` (`bench/jit_trace.lua`) — a standalone tarantool
script that attaches a `jit.attach('trace')` listener over each hot
encode/decode path and asserts no aborts in our source files fall into
the fatal set (NYI bytecode, blacklisting, persistent type instability).
Runs outside luatest because on macOS arm64 the test framework exhausts
JIT mcode pages before the test body runs, masking real abort reasons.
Two fixes shipped to make all 13 scenarios pass:
- `decode_varint` grew a 1-byte fast path. Before, calling it from a
hot decode loop pulled an inner `while true do` into the caller's
root trace, which got blacklisted after enough retries.
- `pb.finalize_message` now precomputes `desc.oneofs_list` (array
form) and the runtime-mode codec iterates it with ipairs instead
of `pairs(desc.oneofs)`. `pairs()` over a hash-keyed table compiles
to bytecode ISNEXT, which is NYI in LuaJIT 2.1.
The gate also reports interpreter-bridge counts as a benchmark-quality
metric. Decoders show 0-4 bridges per run depending on JIT timing —
caused by side traces returning from inlined `decode_varint` calls,
which LuaJIT 2.1 can't stitch back cleanly. Small per-call overhead on
the multi-byte slow path, structural to the engine.
Scope caveat: map fields encode via `pairs()` and remain off-trace —
pinned by the gate's last scenario so we notice if upstream lifts the
restriction.
Initial commit: protoc-gen-tarantool plugin + pb runtime
A protoc plugin (Go) and a pure-Lua + LuaJIT-FFI runtime that give
Tarantool a complete proto3 + gRPC stack. Two codegen modes (full
inline / runtime descriptor), 226-test luatest suite, 18-fixture
mainline-protoc interop corpus, JSON codec, well-known types,
gRPC client/server factories, runtime .proto parser, microbench
harness with allocation regression gate.
Covers PLAN.md M1-M5. Module is `pb` (not `protobuf`) to avoid
colliding with Tarantool's built-in encode-only `protobuf` module.