Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Running the protobuf decode benchmark

crates/twg-codec-protobuf/benches/decode.rs is the measurement gating ADR-0046. It does not run today — every arm is a todo!(), because there is nothing behind it yet. This is the runbook for getting from here to a number.

Do this first: check the premise, outside the workspace

Before implementing anything, answer the question the whole plan rests on: does a zero-copy descriptor-driven parser actually beat prost-reflect by enough, on schemas like yours, to justify vendoring and maintaining a divergence?

That needs none of our code. In a scratch crate:

cargo new --lib pbbench && cd pbbench
# dev-deps: criterion, prost, prost-types, prost-reflect,
#           and the upstream SDK with its parser feature enabled
protoc --include_imports \
       --descriptor_set_out=wide.pb \
       -I ../thalweg/crates/twg-codec-protobuf/proto \
       ../thalweg/crates/twg-codec-protobuf/proto/wide_128.proto
cargo bench

Decode the same payload both ways and compare. An afternoon’s work that de-risks weeks: if zero-copy does not win decisively on a 128-field schema, the vendoring plan changes and you have saved the implementation.

Use wide_128.proto. A narrow schema flatters reflection-based decoding and will understate the gap enough to talk you out of the right answer.

What the in-repo benchmark needs, in order

  1. Dev-dependencies in crates/twg-codec-protobuf/Cargo.toml: criterion, prost, prost-types, prost-reflect, and the upstream SDK behind its parser feature. The layering gate exempts [dev-dependencies] by design — a vendor SDK there is legitimate and ships to nobody; the same crate in [dependencies] would fail CI, correctly.
  2. Compiled descriptors. The .proto fixtures exist in proto/. A build script or a committed artefact must turn them into a FileDescriptorSet:
    protoc --include_imports --descriptor_set_out=schemas.pb -I proto proto/*.proto
    
    --include_imports is not optional — without it the well-known types are absent and wellknown.proto fails to resolve, which is the very failure mode being tested.
  3. Payload generation at the three benchmark sizes, deterministic and seeded so runs are comparable across machines and over time.
  4. The four decode paths, replacing the todo!()s: prost-reflect (the floor), the upstream parser (the baseline to beat), and our two candidates — parse-then-walk and fused-into-builders.

Running it

cargo bench -p twg-codec-protobuf
cargo bench -p twg-codec-protobuf -- decode/wide_128   # the deciding case

Criterion writes to target/criterion/. Commit baselines once the numbers are trusted, and wire a CI failure on regression beyond a stated tolerance — a benchmark that gates a decision belongs in CI, not in a document (docs/testing/TESTING.md).

Reading the result

Two independent questions, deliberately measured separately:

Is vendoring worth it? Compare the upstream parser against prost-reflect on wide_128. If the margin is large, vendoring is justified; if not, take prost-reflect and drop the whole plan.

Is fusing worth it? Compare ours_fused against ours_parse_then_walk. If fusing does not beat walking by a real margin, drop the fusion and keep the simpler path — vendoring can still be right while fusion is not.

Bear in mind that fusing cannot eliminate per-record staging: protobuf fields arrive in arbitrary order and repeated fields interleave, so absent fields are only known at end of record. The upside is bounded, which is exactly why it is measured rather than assumed.