HttpArena Isn't a Scoreboard. It's the Oracle.
HttpArena Isn’t a Scoreboard. It’s the Oracle.
HttpArena is the independent HTTP benchmark board — ~30 profiles, one pinned machine class, every framework in Docker under the same load generators. Orisha was not on it. Our internal twobox harness compares static files against nginx on localhost; that is honest work, but it is not HttpArena’s contract.
What this is. We may still land a framework entry upstream — validate-green is real, and the Dockerfile exists. But acceptance on a public board is not the point. The story is bigger than HttpArena: use the compiler to build what you need, not a generic artifact that accepts anything at runtime and pays for that generality on every request.
That is not nostalgia for static sites. Koru software is often on par with or faster than C on dynamic work too — std/kernel:reduce is ~5× the C people actually write on changing data, because the compiler commits to an iteration strategy for that specific op. The old way hopes -O3 saves you. The Orisha way decides while it compiles.
HttpArena is one lens on the same thesis. Orisha numbers look wild on runtime-framework scoreboards because we keep moving work to compile time: pre-rendered wire shapes, static routers that compile away, profile-guided branch reordering, Docker images where the binary is the container. Reviewers read that as cheating; we read it as compilation being configuration — the same shift CI/CD already made at deploy time, now applied to the program itself.
This is one lens on the Orisha way through HttpArena’s oracle: correctness as an external spec, speed as a local feedback loop. The scoreboard is optional; the methodology is not.
The artifact ladder (what you ship)
Modern deploy is CI → immutable artifact. Almost nobody pushes PHP into a live docroot anymore. Orisha was already shaped for that before HttpArena showed up — the server is the build output.
The ladder is not a byte-count contest. It is what sits underneath your program:
| Shape | Underneath | What you ship |
|---|---|---|
Hello on Linux scratch | A host kernel runs your binary; the image has no OS in it — Dockerfile in source | 14.8 kB — musl binary only |
| Hello on Unikraft | No Linux kernel — your app, a TCP stack, and just enough scheduler to bind a port | ~547 KB bootable image |
korulang.org in scratch | Host kernel; binary embeds 1,119 files | ~24 MB, one statically-linked file |
| korulang.org on Unikraft | No Linux kernel — whole site, same bytes | ~25 MB bootable |
| korulang.org live today | Vercel owns the socket — Orisha wasm reactor is the answer half only; pages baked at compile time (site.k) | wasm module + adapter; x-orisha: reactor on the apex |
Same Orisha seam everywhere: path in, HTTP bytes out. On Linux you also ship the pump. On Vercel the platform is the pump and you ship the compiled answer function. On Unikraft there is no Linux at all — only the program and the network stack it needs to speak HTTP. “Smaller on Unikraft” means there isn’t a general-purpose OS under you — not that every column sorts cleanly by inode count.
That is the cohesive story HttpArena sits inside:
- Scratch — the CI artifact is the server; the container is the binary because there is nothing else to add.
- Unikraft — push the same idea one layer down: no Linux userspace, no container runtime, no sshd, no second process — boot straight into your handler.
- Vercel reactor — push it sideways: no long-lived server process anywhere; the site is a wasm module instantiated on demand, and this website is already served that way (
koruc site.k deploybakesbuild/intohandler.wasmand pushes to thekorulang-orgproject).
Scale from hello to full site and the artifact grows because the content moved into the build — not because a mount appeared at runtime. Publishing a post means recompiling. The immutable-deploy world already accepts that trade; Orisha does not pretend the volume still exists.
Compare to the industry default — nginx:alpine, a mounted tree, a container runtime, a kernel you share with everything else on the host — and a 25 MB unikernel or scratch binary serving an entire website is a different category of deploy, even when the megabyte count looks similar to someone counting wrong on purpose.
HttpArena’s pre-render fast paths rhyme with all of the above: compute what the spec requires, emit the wire shape at authoring time, skip the generic server tax. Static embedding is the obvious case; oracle JSON and std/kernel:reduce on changing data are the proof the thesis is not “static sites only.”
What the oracle actually asks for
HttpArena is not “return JSON fast.” Each profile is a specific endpoint shape, a specific port, sometimes TLS, sometimes gzip rotation, sometimes pipelining with sixteen in-flight requests per connection. The harness mounts certificates at /certs, a shared dataset at /data/dataset.json, and probes until something fails loudly.
The first probe is deceptively small:
GET /baseline11?a=13&b=42 → body "55" Plain text. Sum the query parameters. Then it gets harder — POST bodies with Content-Length and chunked encoding, a frag validator that tries 1,047 byte offsets looking for parser lies, JSON slices whose items must match the mounted dataset, TLS 1.3 with AEAD ciphers on port 8081, gzip negotiation on dynamic JSON.
We cloned the suite to ~/src/HttpArena, read frameworks/swerver/ and frameworks/genhttp-ioxide/ as reference implementations, and built the Orisha-shaped answer in orisha/lib/httparena.* plus orisha/httparena/ as the board entry.
The handler seam (again)
Same defect as the unikernel hello-world: a host-side handler proc compiles but never attaches. The working pattern wires the handler at the entry site:
orisha:handler = orisha:router(req)
! [GET /baseline11] |> orisha/httparena:baseline(req)
! [GET /pipeline] |> orisha/httparena:pipeline(req) (In the board entry that assignment lives in a host-embedded .k file; the blog shows the Koru surface only.) Route bodies live in the tor companion pair (lib/httparena.k + lib/httparena.kz).
Bugs that looked like slowness
Two separate classes of failure showed up before we ever ran a benchmark.
Correctness first. Bodies allocated with req.allocator but freed through koru_allocator() crashed on the second request. The JSON dataset used defer free(file) and invalidated string slices parsed from that buffer — segfault on /json/. The pump ignored Connection: close, so the frag validator never saw TCP EOF and reported nine failures that were really shutdown semantics.
Then speed. /pipeline returned "ok" and let answer() format Date, X-Powered-By, and the rest on every request. Baseline and JSON went through the same dynamic path — allocPrint, header scans, std.json.Stringify over the dataset walk. Local wrk had Orisha at ~82–83% of ASP.NET Core on those routes while /pipeline was already ahead.
The fix was not “make the router faster.” The router was not the tax. Pre-render the full HTTP message — threadlocal body, minimal keep-alive headers, dataset strings JSON-escaped once at load — the same pattern static routes already used.
That is the Orisha move in miniature. Other stacks turn those choices into runtime middleware and header formatters. Here they are authoring decisions the compiler materializes. HttpArena’s standard-mode rules assume you pay that cost on every request; we often already paid it when the binary was built. It feels like cheating because the benchmark is measuring runtime framework tax — and we keep shrinking what’s left to measure.
After fbd472b, on the same Mac box, same wrk flags, against an HttpArena-shaped ASP.NET Core reference:
| Route | ASP.NET Core | Orisha | |
|---|---|---|---|
/pipeline | 162k req/s | 165k | 102% |
/baseline11 GET | 162k | 165k | 102% |
/json/10?m=1 | 155k | 161k | 103% |
Read those numbers with the discipline attached. Mac wrk tops out around 150–170k on trivial routes here. HttpArena’s published leaders run gcannon on a 64-core Threadripper with POST rotation and -p 16 pipelining — millions of requests per second. We have not measured that yet. The table above is development feedback, not a board score.
Gate zero: validate green
What we can claim today is correctness on the subscribed profiles:
=== Results: 37 passed, 0 failed === That run includes exhaustive frag (9 shapes, 1,047 offsets each), json-comp gzip rotation, json-tls with TLS 1.3 and the mounted certificate, pipelined responses, and the baseline anti-cheat checks. Local Docker rebuild via ./validate-local.sh; log at orisha/httparena/results/validate-local-latest.txt.
We wrote the evidence contract down in orisha/httparena/DISCIPLINE.md so future-us cannot accidentally blog a laptop wrk number as proof:
- Ground truth —
validate.sh+ gcannon/zrk on Linux - Pre-board — gcannon mirroring profiles on Linux with
--build=io-uring - Smoke — Mac
wrk,./compare.sh— never cited as competitive proof
Seven profiles subscribed in meta.json: baseline, limited-conn, pipelined, json-comp, json-tls, latency-1m, latency-10k.
What shipped where
| Artifact | Status |
|---|---|
orisha @ fbd472b | Pushed to korulang/orisha |
HttpArena/frameworks/orisha/ @ 6e77221 | Local only — upstream push needs a PR to MDA2AV/HttpArena |
| Board RPS numbers | Unmeasured |
| Public “fastest” claim | Not yet — silence preferred |
The framework entry Dockerfile builds from the Orisha repo, wires ports 8080 (cleartext) and 8081 (H1 TLS), and runs the same main.k we iterate locally. Getting it onto the public leaderboard is paperwork plus hardware, not more Koru.
HttpArena is the oracle; orisha/httparena/DISCIPLINE.md is where we write down what counts as evidence.