~bigbes/tarantool

tarantool-protobuf

ref: b7e97d6a1ed1ba37d46a9cfc85c32b8ced751cf6 tarantool-protobuf/bench/c_accel/Makefile -rw-r--r-- 1.3 KiB
d4dbd2f3 — Eugene Blikh 3 months ago
bench: C-acceleration spike — measure four Lua↔C boundaries

Adds bench/c_accel/ with hello.Person codecs at four boundary
strategies and a harness comparing them across 10B–100KB:

  S1 pure Lua (full mode) — existing baseline
  S2 prim.c + prim_ffi.lua — per-primitive FFI calls
  S3 generic_codec.c — one C call per message, descriptor-walking
  S4 person_codec.c — hand-written, no dispatch, upper bound

Plus ffi_probe.lua, a microbenchmark decomposing FFI call cost.

Headline numbers (×L = speedup vs pure-Lua full mode):

  ENCODE       S2     S3     S4
  10B        0.99   3.29   3.79
  100B       0.93   3.12   3.46
  1KB        0.82   5.33   4.94
  10KB       0.60   3.53   2.86
  100KB      0.63   3.49   2.85

  DECODE       S2     S3     S4
  10B        0.29   2.59   2.54
  100B       0.33   2.94   2.95
  1KB        0.39   7.22   7.62
  10KB       0.38   9.64  10.23
  100KB      0.37  10.87  11.33

S3 lands within ±15% of S4 at every size and beats it on encode
at 1KB+: the descriptor-walking loop is uniformly branch-predictable;
hand-written has more divergent per-field paths.

S2 loses to pure Lua at every size ≥1KB on encode and at every size
on decode. FFI cost decomposition (ffi_probe.lua):

  bare FFI call into ffi.load lib        33 ns
  pointer-returning FFI                  73 ns
  + v_out[0] read + tonumber            118 ns
  ffi.C.memcmp (for comparison)          60 ns
  ffi.cast(const uint8_t*, str)         156 ns
  ffi.string(p, 32)                      23 ns
  pure-Lua varint decode                 75 ns
  read_varint in tight traced loop       59 ns (floor)

Per-call FFI dispatch into a ffi.load'd lib is ~60–75 ns — the
same order of magnitude as pure-Lua varint decode. The "win" from
going to C only materializes when you cross the boundary ONCE per
message, not per primitive.

C encode plateaus at ~2.5–2.9 GB/s from 1KB upward; bottleneck
moves to Lua table reads and output string allocation. C decode
hits 2.5 GB/s at 100KB, while pure-Lua decode hits a per-byte
cliff (158 k msg/s @ 1KB → 2.3 k @ 100KB).

Architecture implication for pf6: the generic C runtime (ra6) gets
the full perf envelope; codegen-emitted per-message C (c0i) earns
≤15% headroom and goes the wrong way at scale. Per-primitive FFI
is a non-starter.

Required impl pattern in any future ra6: cache per-field stack
indices for repeated/packed arrays for the duration of
decode_message — the naive lazy-getfield variant was 2× slower
than hand-written at 100KB (see bench/c_accel/README.md).

beads-tarantool-protobuf-04c