This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

decouple README from the originating workload

benchmark methodology moves to docs/benchmarks.md (workload shape, what
runs, measurement caveats); README describes zio on its own terms and
links out. the fan-in ingest parameters keep their provenance in the
benchmarks doc rather than defining the project.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

+107 -31
+40 -29
README.md
··· 3 3 an evented `std.Io` backend for zig, built to beat thread-per-connection under 4 4 ReleaseSafe — with all safety checks on. 5 5 6 - born from running ~2,800 websocket subscribers on both `Io.Threaded` and 7 - `Io.Evented` in production ([zlay](https://tangled.org/zat.dev/zlay)) and 8 - watching the experimental upstream backend fail. full context: 9 - 10 - - `relay/docs/evented-retrospective-2026-07-08.md` — why upstream Evented failed us 11 - - `relay/docs/evented-empirics-2026-07-09.md` — lab notes: what's actually in 0.16/master 12 - - `relay/docs/evented-runtime-plan.md` — the plan this repo executes 6 + zig 0.16 shipped `Io.Threaded` as the only production backend; `Io.Evented` 7 + is experimental, has no networking, and its fiber machinery crashes under 8 + ReleaseSafe. zio fills that gap as an ordinary package — no compiler fork, no 9 + stdlib patch. 13 10 14 11 ## design 15 12 16 - - **ordinary package, no compiler fork.** `std.Io` is a plain vtable; zio is 17 - just another implementation, like `Io.Threaded` is. 18 - - **hybrid by composition.** zio wraps an inner `Io.Threaded` and delegates 13 + - **ordinary package.** `std.Io` is a plain vtable; zio is just another 14 + implementation, the same way `Io.Threaded` is. 15 + - **hybrid by composition.** `Zio` wraps an inner `Io.Threaded` and delegates 19 16 everything to it except sockets + sleep + concurrency, which run on an 20 17 evented engine. anything unimplemented falls back to a thread — graceful 21 18 degradation by construction. 22 - - **ReleaseSafe-first.** engine v0 is a readiness loop (epoll/kqueue) driving 23 - per-connection state machines — no fibers, no stack switching, nothing the 24 - safety checker can object to. fibers are a *measured upgrade* behind a build 25 - option, adopted only if the scoreboard says they win. 19 + - **ReleaseSafe-first.** the engine core is a readiness loop (epoll/kqueue) 20 + driving per-connection state machines — no fibers, no stack switching, 21 + nothing the safety checker can object to. fibers are a *measured upgrade* 22 + behind a build option, adopted only if the scoreboard says they win. 26 23 - **scoreboard before runtime.** `Io.Threaded` is the permanent reference 27 24 column. no engine code lands without a benchmark delta. 28 25 29 - ## bench 26 + ## surfaces 27 + 28 + - `zio.Engine` — the readiness-loop core: register non-blocking fds with 29 + handlers, run one engine per core. callback-style. 30 + - `zio.Zio` — the hybrid `std.Io` implementation (delegating skeleton today; 31 + evented ops splice in as the engine grows). 32 + 33 + ## benchmarks 34 + 35 + methodology in [docs/benchmarks.md](docs/benchmarks.md), results in 36 + [docs/baselines.md](docs/baselines.md). headline (linux, ReleaseSafe, 37 + 2,800 conns): ~200× less RSS than thread-per-conn, O(cores) threads instead 38 + of O(conns), higher throughput, and a full 2,800-connection reconnect storm 39 + re-established in ~115 ms. 30 40 31 41 ``` 32 - zig build bench -Doptimize=ReleaseSafe -- --conns 100 --seconds 5 42 + zig build bench -Doptimize=ReleaseSafe -- --backend zio --conns 2800 --seconds 5 --storm 33 43 ``` 34 44 35 - zlay-shaped workload: N loopback TCP connections, server pumps length-prefixed 36 - frames, one reader per connection via the selected `Io` backend. reports 37 - aggregate frames/s, bytes/s, max RSS, and OS thread count (linux). 38 - 39 - raw TCP, not websocket, on purpose: the runtime doesn't care about ws framing, 40 - and zero deps keeps the scoreboard honest. 41 - 42 45 ## status 43 46 44 - - [x] repo + bench harness 45 - - [x] threaded baseline numbers (linux/orbstack; macos is smoke-only) 47 + - [x] bench harness + `Io.Threaded` baselines 46 48 - [x] hybrid delegating skeleton (`Zio` wrapping `Io.Threaded`) 47 - - [x] engine v0: readiness loops (epoll/kqueue) — at 2,800 conns: ~200× less 48 - RSS, O(cores) threads, higher throughput than threaded (docs/baselines.md) 49 + - [x] engine v0: readiness loops (epoll/kqueue), per-conn state, reconnect 50 + storms, interest modification, non-blocking connect 49 51 - [ ] fibers behind a flag (vendored from zig master, gauntlet-tested) 50 - - [ ] io_uring engine + epoll fallback (docker default seccomp blocks uring) 51 - - [ ] zlay integration behind `const Backend` 52 + - [ ] io_uring engine + runtime detection with epoll fallback (default docker 53 + seccomp blocks io_uring) 54 + - [ ] first production consumer integration 55 + 56 + ## origins 57 + 58 + zio came out of running ~2,800 production websocket subscriptions on both 59 + `Io.Threaded` and `Io.Evented` and watching the experimental backend fail in 60 + ways that forced a retreat to thread-per-connection. the write-ups that led 61 + here (retrospective, stdlib empirics, plan) live with that project's docs; 62 + the short version is in [docs/benchmarks.md](docs/benchmarks.md#the-workload).
+1 -1
bench/main.zig
··· 1 - //! scoreboard benchmark: zlay-shaped ingest workload. 1 + //! scoreboard benchmark: fan-in ingest workload (see docs/benchmarks.md). 2 2 //! 3 3 //! N loopback TCP connections; an in-process server pumps fixed-size frames 4 4 //! down each; readers consume them through the selected backend:
+1 -1
docs/baselines.md
··· 1 1 # scoreboard baselines 2 2 3 3 the reference column every zio engine must beat. same binary, same harness 4 - (`bench/main.zig`), ReleaseSafe unless noted. 4 + (`bench/main.zig`), ReleaseSafe unless noted. methodology: [benchmarks.md](benchmarks.md). 5 5 6 6 ## Io.Threaded — zig 0.16.0 7 7
+65
docs/benchmarks.md
··· 1 + # benchmarks 2 + 3 + the scoreboard methodology: what the bench measures, why it's shaped the way 4 + it is, and how to run it. results live in [baselines.md](baselines.md). 5 + 6 + ## the workload 7 + 8 + the bench models a **fan-in ingest server**: many long-lived TCP connections, 9 + each delivering a continuous stream of small framed messages, all consumed by 10 + one process. this shape shows up in firehose consumers, market-data feeds, 11 + telemetry collectors, and message brokers. 12 + 13 + the concrete parameters come from the workload that motivated zio — 14 + [zlay](https://tangled.org/zat.dev/zlay), an AT Protocol relay that holds 15 + ~2,800 websocket subscriptions and was paying for them with a thread apiece: 16 + 17 + - ~2,800 concurrent connections (one per upstream host) 18 + - small frames (256 bytes here; CBOR events in the real thing) 19 + - connections are long-lived but churn in storms (a reconnect cron 20 + re-announces ~1,800 hosts at once) 21 + - consumers must reassemble frames across reads (framed protocol over TCP) 22 + 23 + zio itself is workload-agnostic; the bench is opinionated so the numbers mean 24 + something. 25 + 26 + ## what runs 27 + 28 + one process, loopback only (nothing leaves the machine): 29 + 30 + - an in-process server accepts N connections and pumps frames down each, 31 + unthrottled (throughput-ceiling measurement) 32 + - N readers consume through the selected backend: 33 + - `--backend threaded`: one blocking reader task per conn via 34 + `std.Io.Threaded` — the reference column, always 35 + - `--backend zio`: readiness loops (`--loops`, default = cores), readers as 36 + per-conn state machines with frame reassembly 37 + - `--storm`: after the measured window, tear down every reader and 38 + re-establish all of them via simultaneous non-blocking connects; reports 39 + time to full recovery and post-storm delivery rate 40 + 41 + raw TCP, not websocket, on purpose: the runtime doesn't care about ws framing 42 + and zero deps keeps the scoreboard honest. 43 + 44 + ## how to run 45 + 46 + ``` 47 + zig build bench -Doptimize=ReleaseSafe -- --backend zio --conns 2800 --seconds 5 --storm 48 + ``` 49 + 50 + high-conn runs happen on linux (docker is fine; loopback stays in the 51 + container). macos is a smoke platform — its loopback backlog limits cause 52 + connect storms to time out at ~1,000 conns. 53 + 54 + ## measurement notes 55 + 56 + - frames/s is derived from a byte counter delta across the timed window; 57 + reassembly counts complete frames via per-conn carry state. 58 + - RSS via `getrusage` max-RSS (KiB on linux, bytes on macos). thread count 59 + via `/proc/self/status` (linux only). 60 + - the threaded reader path pays `Io.Reader` interface overhead per frame while 61 + the zio path drains 64 KiB per wakeup — batching is a genuine architectural 62 + advantage of readiness loops, but keep it in mind when quoting throughput 63 + multiples. the structural results (RSS, thread count) don't depend on it. 64 + - emulated-arch runs (rosetta) are correctness evidence only; never quote 65 + their timings.