LogoPly

Benchmarks

Reproducible throughput, latency and overhead measurements

Benchmarks

Every number on this page comes with its machine, method and spread, so you can judge it and rerun it yourself.

  • Host: AMD Ryzen 5 5600H (6C/12T), CachyOS, governor performance
  • Toolchain: Go 1.26.5, fasthttp v1.77.0
  • Comparison servers: Fiber v2 2.52.16, Fiber v3 3.6.1, Echo v5 5.4.4
  • Load generator: wrk on the same host, so client and server share cores
  • Server settings: GOMAXPROCS=12, release build, logging disabled

Because the load generator runs on the same machine, absolute throughput is lower than a two-host setup. Relative comparisons remain useful; everything below is reproducible from this repository.

How the sweeps were run

  • Round-robin (bench/fairparams_rr.sh): each rep runs every framework in sequence, so thermal drift hits all of them equally. Median of 3 reps, 10s per rep, reported with min-max.
  • Same-host caveat: wrk -t12 plus a 12-thread server oversubscribes a 6-core CPU. Spread of 5-15% is normal in that configuration; differences smaller than the spread are noise, not wins.
  • In-process measurements (no sockets, no syscalls) are deterministic and are the right way to compare framework overhead.

Dynamic route: GET /user/42

c = 512, t = 12 (oversubscribed)

FrameworkRPS (median)min-maxp99
fasthttp462,022455,666-464,5434.97 ms
fiber v2453,277445,724-456,4005.08 ms
ply450,054433,408-458,2685.07 ms
fiber v3441,530440,756-450,7525.25 ms
net/http296,132292,545-296,6789.46 ms
chi275,713271,759-277,01211.39 ms
gin275,065258,416-277,73611.69 ms
echo270,836259,125-274,00911.26 ms

Ply is 2.6% behind fasthttp and inside 2% of both Fiber versions: the fasthttp-based group is interchangeable at this concurrency. Every fasthttp-based server is ~1.6x the net/http-based group.

c = 200, t = 6 (thread-matched)

FrameworkRPS (median)min-maxp99
fasthttp453,821444,840-459,0262.20 ms
fiber v2446,904440,859-450,8912.16 ms
ply442,483439,822-442,8562.20 ms
fiber v3439,446431,254-443,0112.24 ms
net/http285,608285,363-285,9893.52 ms
gin264,289262,677-265,4953.72 ms
chi263,415261,926-266,7783.68 ms
echo256,752256,026-257,4983.66 ms

Within 2.5% of fasthttp and 1% of Fiber, with sub-1% spread on Ply. This is the configuration to trust for framework comparisons: parity with the other fasthttp-based servers, which is the expected result for a thin layer on the same engine.

Plaintext and JSON

We do not publish a separate plaintext/JSON table. Those workloads barely exercise routing: they measure fasthttp and the JSON encoder, not the framework, and the differences between the fasthttp-based servers sit inside run-to-run noise. The dynamic route above is the comparison that actually separates the routers.

Route lookup (CPU only, deterministic)

go test -bench on the router, no network, 200,000 iterations:

Benchmarkns/opallocs
Static dispatch110
120 parametric routes, slot lookup (FindInto)450
Parametric dispatch72-810
Wildcard, compiled fast path730
Wildcard, radix fallback760

Absolute values shift with CPU frequency between sessions, so compare within a run, not across machines. Route params land in eight inline slots on the context, so a matched request allocates nothing (0 B/op, 0 allocs/op above). Correctness is enforced by a differential fuzz test that compares the slot API against the original map-based lookup over random paths and methods.

Free per-request path parsing

fasthttp parses the request URI and normalizes the path on the first ctx.Path() call, which dominates handler CPU for trivial routes. Ply uses the raw request target whenever normalization provably cannot change it and falls back otherwise (1,000,000 iterations):

ns/op
ctx.Path() (parse + normalize)105
Ply guarded fast path14

A 50,000-target differential test asserts the fast path is byte-identical to ctx.Path(); queries and fragments are trimmed the same way.

Framework overhead per request (in-process, no syscalls)

Each framework is invoked as a fasthttp.RequestHandler with a reused RequestCtx (GET /user/42), which isolates framework work from the kernel. Median of 3 runs, 300,000 iterations each:

Casens/request
bare fasthttp (path + switch)173
Ply, param route180
Ply, static route144
Fiber v2287
Fiber v3305
Ply, JSON response605

The bare static baseline (63 ns) never touches the path, so it does not pay for URI parsing; the comparison that matters is the param row, where both sides must read the path. Ply is within 4% of a hand-written path-plus-switch handler, meaning the router, context pool, middleware chain and recovery together are effectively free, and ~37% thinner than Fiber on the same engine.

Reproduce

# deterministic router + handler benchmarks
go test ./internal/router -bench=. -benchtime=100000x -benchmem -run='^$'
go test . -bench='Benchmark(RoutePath|CtxPath)' -benchtime=1000000x -run='^$'
cd bench && go test ./inproc -bench=. -benchtime=300000x -run='^$'

# network sweeps (needs wrk, postgres and built bench servers)
bash bench/fairparams_rr.sh 3 10s
WRK_T=6 WRK_C=200 bash bench/fairparams_rr.sh 3 10s

Raw results and profiling notes live in bench/results/report.md.