ac9b9c13
codegen+wire: split length-prefix emission + 2-byte varint fast path
Two complementary encode optimizations: codegen + runtime writers no longer pay a per-field string concat for length-prefixed fields, and encode_varint_slow now handles 2/3/4-byte values without dropping into the uint64 cdata path. Composite bench/bench.lua, full mode: 1KB encode: 229 K -> 332 K msgs/s (1.45x) 10KB encode: 38 K -> 56.5 K (1.49x) 100KB encode: 4.1 K -> 6.7 K (1.64x) Runtime mode now matches full mode at 10KB+: 1KB encode: 203 K -> 276 K (1.36x) 10KB encode: 35 K -> 55 K (1.56x) bench/shapes_bench.lua biggest swings (both modes): packed-int32x1000 enc: 1.7 K -> 13 K (7.0-7.6x) packed-int32x100 enc: 21 K -> 113 K (5.3x) scalar-heavy enc: 582 K -> 1.0 M (1.7x) Per-helper, bench/wire_bench.lua: encode_varint(150) [2-byte]: 529 ns -> 68 ns (7.8x) encode_varint(20K) [3-byte]: 841 ns -> 88 ns (9.6x) encode_tag(16, LEN) [2-byte]: 531 ns -> 78 ns (6.8x) encode_string(200B) [2-byte L]: 560 ns -> 109 ns (5.1x) encode_varint(127) [1-byte]: 29 ns -> 28 ns (no regression) 1. Split length-prefix emission. Length-delimited fields (string/bytes scalars, nested messages, packed scalars/enums) used to emit `tag → encode_len(body)` where `encode_len(body)` returns `encode_varint(#body) .. body`. The string concat allocated a copy of the body per field. Now the codegen and runtime writers emit three separate `out` slots — `tag`, `varint(#body)`, `body` — and let `table.concat(out)` join them in one pass at the end of encode. Applied to inline.go (full-mode codegen) for: singular and repeated nested messages, singular and repeated string/bytes scalars, packed scalars, packed enums. Applied to codec.lua build_writer / build_ repeated_writer for the same set. Maps still go through encode_len pending a separate pass. 2. encode_varint_slow Lua-number fast paths. The outer encode_varint stays at the tiny `1-byte check + tail call` shape that LuaJIT inlines into hot traces. The slow function (not inlined into hot traces, so its body size is unconstrained) now handles non-negative Lua numbers up to 2^28 directly via bit.rshift / string.char with no cdata allocation. Values in [2^28, 2^53) emit one byte and recurse on the smaller residue. Only cdata inputs, negative Lua numbers (sign-extended to 10-byte varint), and the rare > 2^53 case still take the uint64 cdata loop. Net effect: every multi-byte varint encode that fits in a Lua number — including the length prefix for any string >= 128 bytes, every tag for field IDs >= 16, and every negative-zigzag sint — drops from ~500 ns to ~70 ns. 497/497 luatest pass. 23/23 jit-trace gates pass (the two new multi-byte varint gates added in the bench infra commit confirm encode_varint_slow JIT-compiles cleanly). Conformance suite (binary + text) shows 1478 expected passes, 0 unexpected failures.
Eugene Blikh <bigbes@gmail.com> — 2026-05-16 14:44:23 UTC
Commit ac9b9c1302c14cce6718a4f6e8d7ce84c5b7f1df —
view raw patch
Parent(s):
6d0ec7bf
| File | Status | + | − |
|---|---|---|---|
cmd/protoc-gen-tarantool/internal/gen/inline.go
|
M | +40 | -6 |
examples/expected/full/conformance/conformance_pb.lua
|
M | +40 | -19 |
examples/expected/full/hello/hello_pb.lua
|
M | +66 | -25 |
examples/expected/full/protobuf_test_messages/proto3/test_messages_proto3_pb.lua
|
M | +234 | -80 |
runtime/pb/codec.lua
|
M | +44 | -7 |
runtime/pb/wire.lua
|
M | +59 | -7 |