Cross-client benchmark of three Swarm Bee API clients: bee-go, bee-rs, bee-js.
Every byte/sec figure produced by this bench is client ↔ local Bee node over loopback HTTP, NOT real Swarm-network throughput.
- Uploads measure how fast the client pushes data into the local Bee node's store under deferred-upload mode (
Swarm-Deferred-Upload: true, the Bee server-side default). The chunks are still being pushed to the Swarm network in the background after the call returns.- Downloads of references uploaded earlier in the same run hit the local Bee cache. Numbers like "253 MB/s" are local-store reads, not network fetches.
See FINDINGS § measurement scope for how to re-run with
deferred: falsefor real-network numbers.
| Doc | What's inside |
|---|---|
| Live HTML report | Interactive Chart.js view served from GitHub Pages — open directly in a browser. |
| Latest report (markdown) | Auto-generated per-case tables, scoreboard, sparklines, latency-vs-size linear fits. The numerical truth. |
| Latest report (HTML) | Same as the live report above, but the raw HTML file in this repo. |
| FINDINGS | Hand-written qualitative observations (F1–F21) — what the numbers actually mean, where the gaps come from, what to investigate. |
| README | How to run the bench, env vars, layout. |
| bench-spec.json | Source of truth for cases, sizes, iter counts. |
| aggregate.json | Machine-readable form of report.md. Diff two snapshots with scripts/compare.mjs. |
| report.csv | Flat CSV — one row per (case, param, runner). For spreadsheet / pandas analysis. Regenerated by node scripts/export-csv.mjs. |
From the latest run (see report.md § Scoreboard for the full table):
| Runner | Wins | Overall ratio | CPU | HTTP-stack (Calibration + Pin) |
|---|---|---|---|---|
| bee-rs | 24 | 1.11x | 1.26x | 1.00x |
| bee-go | 10 | 1.32x | 1.15x | 6.04x (Calibration), 2.35x (Pin) |
| bee-js | 0 | 4.48x | 30.9x | 14.5x (Calibration), 2.58x (Pin) |
- bee-rs leads on every group except CPU in the snapshot above (which is against bee-rs 1.1.0); the bee-rs 1.2.0 secp256k1 swap (2026-05-04) flips CPU as well — sign 3.57×, verify 4.50×, identity 2.68× (F5 / F15 / F17)
- bee-js is 947x slower than bee-go on ECDSA verify (F15)
- bee-go is unexpectedly 2.93x slower on stream-dir uploads (F19)
- HTTP-stack pattern: reqwest pools connections better than Go's
http.DefaultClientand far better than axios's keepAlive=false default (F18)
Direct links to each finding in ../FINDINGS.md:
Crypto / CPU:
- F2 — ECDSA backends are not equivalent
- F3 — eth-envelope ECDSA scheme is identical across clients
- F6 — bee-js keccak chunker plateau
- F8 — bee-rs fastest on CPU (ECDSA flipped in v1.2.0)
- F15 — ECDSA verify in bee-js is 947x slower (bee-rs 1.2.0 ties bee-go)
- F16 — Mantaray lookup is essentially tied between bee-go and bee-rs
- F17 — cpu.identity.create is another secp256k1 backend revealer
HTTP / network:
- F10 — Bee node dominates upload wall clock ⚠ not Swarm-network speed
- F11 — Download is bandwidth-bound, ranking flips ⚠ local-cache hit
- F12 — /chunks endpoint is the wallclock killer on Sepolia
- F18 — Pin endpoint round-trips are 2.4x faster on bee-rs
- F20 — Tag bookkeeping is cheap on all three
- F21 — Updated scoreboard summary
Client-API gaps:
- F1 — bee-rs has no streaming raw-bytes upload
- F7 — bee-js holds significantly more memory during chunking
- F9 — encryption-aware offline chunking missing in all three
- F13 / F13b — SOC writes: bee-go ~70% slower than bee-rs
- F13c — net.soc isn't a useful gauge of the v1.2.0 ECDSA swap
- F14 — feed read fails 404 in same iter
- F19 — bee-go is unexpectedly slow on stream-dir upload
Snapshots of earlier runs are preserved locally by ./scripts/preserve-run.sh <label> into results/<label>/. They contain their own aggregate.json / report.md / report.html plus the raw per-runner JSONs. They are not pushed to the remote — the per-runner JSONs are large and the canonical view here is the latest aggregate. To see a snapshot, regenerate it locally with ./scripts/run-all.sh + node scripts/aggregate.mjs.
Once you have two aggregate.json files locally:
node scripts/compare.mjs \
results/<old-snapshot>/aggregate.json \
results/<new-snapshot>/aggregate.json \
--out results/compare.mdCompare report shows per-runner geomean shift and per-row deltas, flagging anything > ±20% with ⚠.