~bigbes/tarantool

tarantool-protobuf

be42fabbaa3f305b5bb6e60bc667da04e5788547 — Eugene Blikh 3 months ago a6eebdb
bench: record u39 (table.new pre-sized result) post-mortem

Attempted emitting table_new(0, N) for decode result tables. Tests
passed, JIT passed, but small-payload decode (10B, 100B) regressed
15-18% because the table.new upvalue-call cost exceeds any rehash
savings at that scale, and the small case never reaches the first
rehash anyway. Large-payload decode flat. Confirms drm's claim that
alloc shape is not the bottleneck on the bench corpus.

Code change reverted; only the writeup lands.

beads-tarantool-protobuf-u39
1 files changed, 48 insertions(+), 0 deletions(-)

M bench/PERF_LOG.md
M bench/PERF_LOG.md => bench/PERF_LOG.md +48 -0
@@ 84,6 84,54 @@ macOS arm64.

<!-- Append new entries below this line. Newest at the top. -->

## 2026-05-18 — u39: table.new(0, N) for result tables (REVERTED)

**Task:** [tarantool-protobuf-u39] Decoder: emit `table.new(0, N)` for
result tables. The thesis was that pre-sizing the result table's hash
part to the message's field count would avoid the rehash that
fresh-`{}` tables pay as fields are added.

**Change attempted:** Header now `require('table.new')` with a pcall
fallback so plain-Lua loads still work; `emitInlineDecode` allocates the
result via `table_new(0, len(m.Fields))` instead of `{}`. 745/745 tests
pass. JIT 37/37.

**Bench (Person full decode, msgs/s, median-of-3 vs post-cch):**

| size  | post-cch  | u39       | Δ        |
|-------|-----------|-----------|----------|
| 10B   | 2,495,633 | 2,111,197 | **-15.4%** |
| 100B  | 2,131,196 | 1,754,540 | **-17.7%** |
| 1KB   | 115,310   | 117,270   | +1.7%    |
| 10KB  | 15,831    | 15,627    | -1.3%    |
| 100KB | 1,620     | 1,617     | -0.2%    |

**Outcome: reverted.** Small-payload decode collapsed. The
`table_new(0, N)` call (through an upvalue, with arguments) is
several times the cost of a `{}` literal, and at 10B/100B the decode
loop only populates 2-4 fields — well under the LuaJIT default hash
size where the first rehash would land. The rehash savings never
materialize because the small case doesn't reach them; meanwhile the
extra call cost is paid every decode.

Large payloads (1KB+) saw no meaningful gain either — by then decode
time is dominated by tag decoding, string extraction, and varint
parsing, not table allocation. The `drm` memory entry already
documented "136 B alloc floor is not the throughput bottleneck"; this
result confirms it from the opposite direction.

**Lesson:** allocation-shape optimizations are only worth it when the
workload spends real time in allocation. The bench corpus does not
exercise that mode; if a workload ever does (lots of tiny messages, or
messages with very wide field sets), revisit then. Sticking with `{}`
also keeps generated code compatible with plain Lua (no LuaJIT-only
require) — small platform-portability win.

**Commit:** none — change reverted. PERF_LOG entry is the only
artifact.

---

## 2026-05-18 — cch: local counter for repeated-field append

**Task:** [tarantool-protobuf-cch] Decoder: use local counter instead of