···2233SDK-level benchmarks for AT Protocol relay infrastructure.
4455-measures the two CPU-bound bottlenecks a relay hits on every incoming commit: **decode** (CBOR + CAR + DAG-CBOR + CID verification) and **signature verification** (ECDSA). same corpora across all SDKs, verified work parity via block counts, error counts, and entry counts.
55+measures the CPU-bound work a relay hits on incoming commits: **decode** (CBOR + CAR + DAG-CBOR + CID verification), **signature verification** (ECDSA), and the combined **relay hot path**. same corpora across SDKs where possible, verified work parity via block counts, error counts, entry counts, and signature validation counts.
6677## what this measures
88···4343| python ([atproto](https://github.com/MarshalX/atproto)) | 33,842 | 163.0 | 9.98 | 0 |
4444| go (indigo) | 15,587 | 75.6 | 9.98 | 0 |
45454646-note: indigo appears in both tables. its number is the same because it always verifies — there is no option to disable it in go-car v1.
4646+note: indigo appears in both tables. its number is the same because the benchmark uses go-car/v2's default `NewBlockReader`, which verifies block hashes.
47474848run-to-run variance is ~30-40%. compare ratios within a single `just bench` run, not across runs.
4949···5555|-----|---------------------|-------|
5656| zig (zat) | yes (v0.2.1+) | `car.read()` verifies by default; `readWithOptions(.{ .verify_block_hashes = false })` to skip |
5757| rust (rsky, rs-car-sync) | yes | `CarReader::new(&mut cursor, true)` — second arg enables verification |
5858-| go (indigo, go-car v1) | yes (always) | no option to disable in v1 |
5858+| go (indigo, go-car/v2 default reader) | yes | this benchmark does not use `WithTrustedCAR(true)` |
5959| rust (jacquard, iroh-car) | no | not implemented |
6060| rust (raw) | no | not implemented |
6161| go (raw) | no | not implemented |
···8787- decode every block as DAG-CBOR
88888989**what zat and indigo do that isn't obvious:**
9090-- enforce size limits (2MB max on blocks field, max block count) — zat matches indigo's limits as of v0.2.2
9090+- enforce size limits (2MB max on blocks field, max block count) — zat has matched indigo's limits since v0.2.2
91919292**what none of them do:**
9393- DAG-CBOR deterministic encoding validation (sorted keys, minimal integers) — indigo's refmt doesn't check this either
···177177178178captured by connecting to the firehose, extracting signed commit blocks from CAR data, and resolving each DID via PLC directory to get the signing key. entries that fail verification are dropped during capture.
179179180180+## relay hot path
181181+182182+the relay hot-path benchmark combines the decode and signature work into one CPU-only pass over raw firehose frames with pre-resolved signing keys. it approximates what a relay does for each incoming commit after identity resolution has already populated a DID key cache:
183183+184184+1. CBOR decode the frame header and payload
185185+2. parse the CAR and verify CID hashes
186186+3. find the commit block
187187+4. strip `sig`, re-encode the unsigned commit as DAG-CBOR
188188+5. verify the commit signature with the cached public key
189189+190190+the corpus is captured from the live firehose, then each entry is validated before it is written. benchmark runs do not hit the network.
191191+192192+### current zig result
193193+194194+_3,173 firehose frames (16.0 MB), 5 measured passes, macOS arm64 (M3 Max)_
195195+196196+| variant | frames/sec (median) | MB/s | live firehose headroom | verified | errors |
197197+|---------|--------------------:|-----:|-----------------------:|---------:|------:|
198198+| zig relay hot path (arena reuse) | 9,793 | 49.5 | 16x | 15,865 | 0 |
199199+| zig relay hot path (alloc per frame) | 10,194 | 51.6 | 17x | 15,865 | 0 |
200200+201201+stage breakdown from the median arena-reuse pass:
202202+203203+| stage | time/frame | share |
204204+|-------|-----------:|------:|
205205+| CBOR decode | <1µs | 0.3% |
206206+| CAR parse + CID verification | 3µs | 3.4% |
207207+| signature verification | 98µs | 96.2% |
208208+209209+signature verification dominates the combined path. this is why the standalone decode benchmarks show very large SDK differences, while the full relay hot path is bounded by ECDSA throughput once commit signatures are included.
210210+211211+```sh
212212+just capture-relay # capture frames + resolve signing keys
213213+just bench-relay # run zig relay hot-path benchmark
214214+```
215215+180216## corpus format
181217182218the fixture file (`fixtures/firehose-frames.bin`) uses a simple length-prefixed binary format:
···205241206242| lang | SDK | version | CBOR engine | CAR engine |
207243|------|-----|---------|-------------|------------|
208208-| zig | [zat](https://tangled.sh/@zzstoatzz.io/zat) v0.2.2 + [k256](https://tangled.sh/@zzstoatzz.io/k256) v0.0.4 | — | hand-rolled | hand-rolled (+ SHA-256 CID verify, size limits) |
244244+| zig | [zat](https://tangled.sh/@zzstoatzz.io/zat) v0.2.18 + [k256](https://tangled.sh/@zzstoatzz.io/k256) v0.0.4 | — | hand-rolled | hand-rolled (+ SHA-256 CID verify, size limits) |
209245| rust | [rsky](https://github.com/blacksky-algorithms/rsky) stack | — | [ciborium](https://crates.io/crates/ciborium) (header) + [serde_ipld_dagcbor](https://crates.io/crates/serde_ipld_dagcbor) (body) | [rs-car-sync](https://crates.io/crates/rs-car-sync) (+ SHA-256 CID verify) |
210246| rust | raw (minicbor + bumpalo) | — | [minicbor](https://crates.io/crates/minicbor) (zero-copy) | hand-rolled (sync) |
211247| rust | [jacquard](https://tangled.sh/@nonbinary.computer/jacquard) | 0.9 | [ciborium](https://crates.io/crates/ciborium) (header) + [serde_ipld_dagcbor](https://crates.io/crates/serde_ipld_dagcbor) (body) | [iroh-car](https://crates.io/crates/iroh-car) (async) |
···269305just bench-sigs-rust
270306just bench-sigs-go
271307308308+# relay hot path
309309+just capture-relay # capture frames + resolve signing keys
310310+just bench-relay # run zig combined relay hot-path benchmark
311311+272312# trust chain verification
273313just verify pfrazee.com # run all three implementations
274314just chart pfrazee.com # run + generate SVG charts to docs/
275315```
276316317317+the Zig package currently targets Zig 0.15.x (`zig/build.zig.zon` sets `minimum_zig_version = "0.15.2"`). if your default `zig` is a 0.16 development build, point the just recipes at a 0.15 binary:
318318+319319+```sh
320320+ZIG=/path/to/zig-0.15 just bench-relay
321321+```
322322+277323## methodology
278324279325- `just capture` connects to the live firehose for ~10 seconds, filters for commits with ops via CBOR header peek (uses zat's CBOR decoder — see fairness notes), writes a length-prefixed corpus
326326+- `just capture-relay` captures raw firehose frames and resolves signing keys up front, so `just bench-relay` is CPU-only and does not hit PLC/PDS services
280327- each benchmark decodes every frame fully: header → payload → CAR → decode every block as DAG-CBOR
281328- zat and indigo additionally SHA-256 verify every block CID
282329- 2 warmup passes, 5 measured passes over the full corpus
+23-8
justfile
···11# atproto firehose benchmarks
22+zig := env_var_or_default('ZIG', 'zig')
2334# capture ~10s of firehose traffic + jetstream event
45capture:
55- cd zig && zig build run-capture
66+ cd zig && {{zig}} build run-capture
6778# run all benchmarks
89bench: _ensure-fixtures
···1314 @echo "corpus: firehose-frames.bin ($(wc -c < fixtures/firehose-frames.bin | tr -d ' ') bytes)"
1415 @echo ""
1516 @echo "--------------------------------------------"
1616- cd zig && zig build run-bench -Doptimize=ReleaseFast
1717+ cd zig && {{zig}} build run-bench -Doptimize=ReleaseFast
1718 @echo "--------------------------------------------"
1819 cd rust-rsky && cargo run --release 2>&1
1920 @echo "--------------------------------------------"
···30313132# run a single language's bench
3233bench-zig: _ensure-fixtures
3333- cd zig && zig build run-bench -Doptimize=ReleaseFast
3434+ cd zig && {{zig}} build run-bench -Doptimize=ReleaseFast
34353536bench-rust: _ensure-fixtures
3637 cd rust && cargo run --release
···5556# run full AT Protocol trust chain verification with timing (all three implementations)
5657verify *ARGS:
5758 @echo ""
5858- cd zig && zig build run-verify -Doptimize=ReleaseFast -- {{ARGS}}
5959+ cd zig && {{zig}} build run-verify -Doptimize=ReleaseFast -- {{ARGS}}
5960 @echo "--------------------------------------------"
6061 cd go-verify && go run . {{ARGS}}
6162 @echo "--------------------------------------------"
···69707071# capture signed commits + public keys from firehose (~10s)
7172capture-sigs:
7272- cd zig && zig build run-capture-sigs
7373+ cd zig && {{zig}} build run-capture-sigs
73747475# run all sig verify benchmarks
7576bench-sigs: _ensure-sigs-fixtures
···8081 @echo "corpus: sig-verify-corpus.bin ($(wc -c < fixtures/sig-verify-corpus.bin | tr -d ' ') bytes)"
8182 @echo ""
8283 @echo "--------------------------------------------"
8383- cd zig && zig build run-bench-sigs -Doptimize=ReleaseFast
8484+ cd zig && {{zig}} build run-bench-sigs -Doptimize=ReleaseFast
8485 @echo "--------------------------------------------"
8586 cd rust-sigs && cargo run --release 2>&1
8687 @echo "--------------------------------------------"
···8889 @echo "============================================"
89909091bench-sigs-zig: _ensure-sigs-fixtures
9191- cd zig && zig build run-bench-sigs -Doptimize=ReleaseFast
9292+ cd zig && {{zig}} build run-bench-sigs -Doptimize=ReleaseFast
9393+9494+# --- relay hot path ---
9595+9696+# capture raw frames with pre-resolved signing keys for full relay hot-path benchmark
9797+capture-relay:
9898+ cd zig && {{zig}} build run-capture-relay
9999+100100+# run decode + CAR CID verification + commit signature verification benchmark
101101+bench-relay: _ensure-relay-fixtures
102102+ cd zig && {{zig}} build run-bench-relay -Doptimize=ReleaseFast
9210393104bench-sigs-rust: _ensure-sigs-fixtures
94105 cd rust-sigs && cargo run --release
···100111101112# build all (no run)
102113build:
103103- cd zig && zig build
114114+ cd zig && {{zig}} build
104115 cd rust && cargo build --release
105116 cd rust-raw && cargo build --release
106117 cd rust-rsky && cargo build --release
···126137_ensure-sigs-fixtures:
127138 @test -f fixtures/sig-verify-corpus.bin \
128139 || (echo "sig corpus not found — run 'just capture-sigs' first" && exit 1)
140140+141141+_ensure-relay-fixtures:
142142+ @test -f fixtures/relay-corpus.bin \
143143+ || (echo "relay corpus not found — run 'just capture-relay' first" && exit 1)