crystal-web-framework-benchmark-2
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 withwrkon the same machine. OnGET /andGET /users/:idthe 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 asART::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, hencesend_jsoninservers/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