Repository navigation
Add fnet-h1 (Go http1.x) entry - #1666
Conversation
Enable fnet.Options{ReusePort: true} so on Linux every sub-reactor
listens on :8080 with its own SO_REUSEPORT socket and the kernel
spreads connections over the loops. Also treat http.ErrServerClosed
from Serve as a clean shutdown from Close.
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
|
This commit expands fnet’s HTTP and WebSocket benchmarks with HTTPS, static file serving, PostgreSQL and Redis endpoints, and additional test profiles. It also tunes WebSocket task execution and event loop usage for better CPU utilization. |
|
/benchmark --save |
|
👋 Benchmark request received. A collaborator will review and approve the run. |
Benchmark ResultsFramework:
Composite score, main against this run, in the board's default view:
The results on main were measured in another session, so a few points up or down can be the machine and not the change. Full log |
Description
fnet-h1 — Go entry
This adds the HTTP/1.1 entry for the
fhttpserver of fnet (github.com/linfeip/fnet, v1.0.1-0.20261027085849).fnet is an event-driven network library for Go: a TCP engine, a
net/http-compatible HTTP/1.x server (fhttp) and a WebSocket server, all on the same set of event loops. It runs a main/sub-reactor model on epoll (Linux) and kqueue (macOS); each connection is served by one task at a time on a per-CPU sharded, lock-free goroutine pool (taskpool) — no goroutine per connection, and an idle connection holds neither a goroutine nor a buffer (the read buffer is borrowed only while its task is reading). fhttp keeps the standard library's parser andhttp.Handlerinterface: the event loop only finds where each request message ends, then the message is parsed and the handler run on a taskpool worker.What the request path looks like:
baseline,json-compand the fixed-rate profiles never leave the loop's thread pool.pipelinedexercises./delay/{ms}sleeps on the task. The pool only wakes a parked worker when none is free, soasync's 16,000 connections each waiting 10 ms hold neither a goroutine nor a buffer.net/httpserver, which closes that connection after the one request. ChunkedPOST /baseline11is the only thing on that path.ReusePortis on: on Linux every sub-reactor listens on:8080with a socket of its own (SO_REUSEPORT), so accepts run in parallel and a connection stays on the loop that accepted it.Entry notes:
type: experimental,engine: fnet— the same classification fnet-ws and uio ended up with; happy to move if that reads wrong.baseline,pipelined,limited-conn,json-comp,async.switchon the path rather thanhttp.ServeMux, and path parameters arrive as plain substrings (the count of/json/{count}, the delay of/delay/{ms}) instead of being resolved intoRequest.PathValue. Query parameters are walked in place withfhttp.ForEachQuery/fhttp.QueryParam, so no map is built per request;baseline11parses the POST body from the buffered bytes and formats the sum into a stack buffer.json-compcompresses with pooled writers at the levels fasthttp'sCompressHandlerBrotliLeveluses by default (gzip 6, brotli quality 4). At equal q values gzip wins: the two bodies are nearly identical on these payloads (1490 vs 1519 bytes for/json/50?m=6) while brotli quality 4 costs several times gzip's CPU. A client that prefers brotli by q value still gets it.NoDelayoff); the only explicit setting isfnet.Options{ReusePort: true}.PPROF_ADDRopts in a pprof listener, unset by default so the measured binary is the same code either way.fhttp.Options.TLSConfig(ftls on the same event loops); the server takesh2out of ALPN and offershttp/1.1, so a client that would otherwise pick h2 falls back to HTTP/1.1. There is no HTTP/2 in any form, h2c included.:8080and subscribes to no TLS profile (json-tls/static-tls), and it serves no static files. None of the five subscribed profiles needs either; an HTTPS listener is oneTLSConfigaway.scratch(golang:1.27-alpinebuild stage); SIGTERM/SIGINT close the server and the process exits 0.scripts/validate.sh fnet-h1passes 14/14 checks; the entry is a singlemain.go, no benchmarking tricks.PR Commands — comment on this PR to trigger (requires collaborator approval):
/benchmark -f <framework>/benchmark -f <framework> -t <test>/benchmark -f <framework> --save/benchmark -f <framework> -t <test> --save/benchmark -f <framework> --compare <other>/benchmark-multiple -f <fw1>,<fw2>,...-tand--savetoo; saved results land in a single commit/benchmark-multiple --save-fneeded: benchmark and save every framework the PR touches/benchmark-test -t <test><test>and save the resultsFor
/benchmark, always specify-f <framework>; the flags combine in any order. Results come back as a comment with a per-profile table of RPS, p99, CPU and memory — one table per framework on multi runs. A new benchmark comment while a run is in flight queues behind it (one deep) instead of cancelling it. For multi-framework PRs (dependency bumps, same-language refactors) prefer/benchmark-multiple, which runs everything in a single job and commits all saved results together, so no run overwrites another.--compareworks on single-framework runs only.What the deltas are measured against. By default, this framework's own results published on
main- answering "did this change help?". When you are tuning a variant or a successor entry,--comparere-bases them on another entry instead:The reply states which baseline it used, and profiles the other framework does not run show
n/arather than a delta.Run benchmarks locally
You can validate and benchmark your framework locally with the lite script — no CPU pinning, fixed connection counts, all load generators run in Docker.
Requirements: Docker Engine on Linux. Load generators (gcannon, h2load, h2load-h3, wrk) are built as self-contained Docker images on first run.