crystal-web-framework-benchmark-2

Crystal web framework benchmark: Spectre vs Toro, router.cr, Spider-Gazelle and athena-routing

Crystal web framework benchmark: Spectre vs Toro, router.cr, Spider-Gazelle and athena-routing

Spectre's bench/ tasks, re-implemented for four other Crystal routing/web frameworks.

Task Request Response
1. text GET / Hello, World! (text/plain)
2. path param + JSON out GET /users/42 {"id":"42","name":"user42"}
3. typed JSON in/out POST /people {"name":"Ann","age":41} {"name":"Ann","age":42}
Server file Framework Routing approach
servers/spectre.cr Spectre (Express-style) radix-like route table
servers/toro.cr soveran/toro 0.5.1 tree-oriented, matchers run per request
servers/router_cr.cr tbrand/router.cr 0.2.8 radix shard
servers/spider_gazelle.cr Spider-Gazelle via its action-controller 9.4.3 annotation routes compiled at build time (lucky_router)
servers/athena_routing.cr athena-framework/routing 0.2.1 compiled route tree, ART::RoutingHandler
servers/raw.cr plain HTTP::Server baseline, task 1 only

Every server binds 0.0.0.0:$PORT, and ./verify.sh checks all five return the identical status and body for the three tasks.

Run

shards install
./verify.sh                 # equivalence check (needs built servers in out/)
./run.sh 6 32               # seconds per scenario, connections; builds with --release --no-debug
python3 summarize.py        # aggregate results/run-*.csv

Needs Crystal >= 1.20 (action-controller 9.4 uses Time.instant), wrk, taskset. Each server is pinned to CPU 0 and wrk to CPU 1, with a 2 s warm-up before each measured run.

Results (3 runs x 6 s, 32 connections, 1 core)

See results/summary.md and the raw CSVs. Median req/s:

Framework GET / GET /users/:id POST /people
Plain HTTP::Server 133,325 – –
Spectre 133,477 120,699 88,478
Toro 128,334 118,527 81,917
router.cr 146,568 115,961 84,627
Spider-Gazelle 128,763 118,945 74,213
athena-routing 124,357 111,643 76,028

How to read this

  • Run-to-run spread is 10-20% (see min-max in results/summary.md) on a shared 2-vCPU VM with wrk on the same machine. On GET / and GET /users/:id the ranges of all five overlap, so none of them can be called faster than another from this data. The baseline is also inside that band.
  • Only the POST task shows a separation that holds in every run: Spectre/Toro/router.cr (about 82-88k) vs Spider-Gazelle/athena-routing (about 74-76k). Spider-Gazelle does content negotiation and a parameter/body-binding layer on top of routing, and the athena-routing handler wraps the callback with exception handling; I did not profile to confirm either explanation.
  • Spider-Gazelle is measured through action-controller, without the template's request logger, gzip handler and MCP endpoint. A full template would be slower. athena-routing is only a router, not a framework, so it was compared as ART::RoutingHandler.
  • Handlers are written the same way where the frameworks allow: build the JSON string, set Content-Length, write once. An earlier version streamed value.to_json(response) in Toro, router.cr and athena-routing; that triggers an early flush and chunked encoding and a 40 ms Nagle/delayed-ACK stall (about 730 req/s). That is a socket artifact, not routing cost, hence send_json in servers/common.cr. Spider-Gazelle and Spectre already do it internally.
  • Not measured: multi-core, many routes (all apps have 3, so radix vs linear matching is not stressed), 404/405 paths, TLS, large bodies.
  • Crystal 1.20.0, Spectre 0.1.0 (from your zip), each dependency at the version in shard.lock.
Repository

crystal-web-framework-benchmark-2

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 5
  • about 3 hours ago
  • November 15, 2026
License

Links
Synced at

Sun, 15 Nov 2026 15:14:26 GMT

Languages